diff --git a/src/mod_magnet.c b/src/mod_magnet.c index 6ec506d1..b8657b40 100644 --- a/src/mod_magnet.c +++ b/src/mod_magnet.c @@ -870,6 +870,18 @@ static handler_t magnet_attract(request_st * const r, plugin_data * const p, buf /* get the script-context */ L = script_cache_get_script(&p->cache, name, r->conf.etag_flags); + if (NULL == L) { + log_perror(r->conf.errh, __FILE__, __LINE__, + "loading script %s failed", name->ptr); + + if (p->conf.stage != -1) { /* skip for response-start */ + r->http_status = 500; + r->handler_module = NULL; + } + + return HANDLER_FINISHED; + } + if (lua_isstring(L, -1)) { log_error(r->conf.errh, __FILE__, __LINE__, "loading script %s failed: %s", name->ptr, lua_tostring(L, -1)); diff --git a/src/mod_magnet_cache.c b/src/mod_magnet_cache.c index 6bd54be8..28cc461e 100644 --- a/src/mod_magnet_cache.c +++ b/src/mod_magnet_cache.c @@ -1,11 +1,11 @@ #include "first.h" #include "mod_magnet_cache.h" -#include "log.h" #include "stat_cache.h" -#include "sys-time.h" +#include #include +#include /* read() */ #include #include @@ -22,7 +22,8 @@ __attribute_cold__ static void script_free(script *sc) { if (!sc) return; - lua_pop(sc->L, 1); /* the function copy */ + if (lua_gettop(sc->L) != 0) + lua_pop(sc->L, 1); /* the function copy */ lua_close(sc->L); free(sc->name.ptr); free(sc->etag.ptr); @@ -46,67 +47,99 @@ void script_cache_free_data(script_cache *p) free(p->ptr); } +__attribute_cold__ +__attribute_noinline__ +static lua_State *script_cache_load_script(script * const sc, int etag_flags) +{ + /* read file and use luaL_loadbuffer() + * eliminate TOC-TOU race w/ independent stat() in stat_cache_get_entry() */ + + stat_cache_entry * const sce = stat_cache_get_entry_open(&sc->name, 1); + buffer_clear(&sc->etag); + if (NULL == sce || sce->fd < 0) { + /*(sce->fd < 0 might indicate empty file, which is not a valid script)*/ + if (NULL != sce) errno = EBADF; + return NULL; + } + const buffer * const etag = stat_cache_etag_get(sce, etag_flags); + if (etag) + buffer_copy_buffer(&sc->etag, etag); + + const off_t sz = sce->st.st_size; + char * const buf = malloc(sz); + force_assert(buf); + + ssize_t rd = 0; + off_t off = 0; + do { + rd = read(sce->fd, buf+off, (size_t)(sz-off)); + } while (rd > 0 ? (off += rd) != sz : rd < 0 && errno == EINTR); + if (off != sz) { /*(file truncated?)*/ + if (rd >= 0) errno = EIO; + return NULL; + } + + int rc = luaL_loadbuffer(sc->L, buf, (size_t)sz, sc->name.ptr); + free(buf); + + if (0 != rc) { + /* oops, an error, return it */ + return sc->L; + } + + force_assert(lua_isfunction(sc->L, -1)); + return sc->L; +} + +__attribute_cold__ +static lua_State *script_cache_new_script(script_cache * const cache, const buffer * const name, int etag_flags) +{ + script * const sc = script_init(); + + if (cache->used == cache->size) { + cache->size += 16; + cache->ptr = realloc(cache->ptr, cache->size * sizeof(*(cache->ptr))); + } + cache->ptr[cache->used++] = sc; + + buffer_copy_buffer(&sc->name, name); + sc->L = luaL_newstate(); + luaL_openlibs(sc->L); + return script_cache_load_script(sc, etag_flags); +} + +static lua_State *script_cache_check_script(script * const sc, int etag_flags) +{ + if (lua_gettop(sc->L) == 0) + return script_cache_load_script(sc, etag_flags); + + force_assert(lua_gettop(sc->L) == 1); + force_assert(lua_isfunction(sc->L, -1)); + + stat_cache_entry * const sce = stat_cache_get_entry(&sc->name); + if (NULL == sce) { + lua_pop(sc->L, 1); /* pop the old function */ + return script_cache_load_script(sc, etag_flags); + } + + const buffer * const etag = stat_cache_etag_get(sce, etag_flags); + if (NULL == etag || !buffer_is_equal(&sc->etag, etag)) { + if (0 == etag_flags) + return sc->L; + /* the etag is outdated, reload the function */ + lua_pop(sc->L, 1); + return script_cache_load_script(sc, etag_flags); + } + + return sc->L; +} + lua_State *script_cache_get_script(script_cache *cache, const buffer *name, int etag_flags) { - script *sc = NULL; - stat_cache_entry *sce; - - for (uint32_t i = 0; i < cache->used; ++i, sc = NULL) { - sc = cache->ptr[i]; - if (!buffer_is_equal(name, &sc->name)) continue; - - if (lua_gettop(sc->L) == 0) break; - force_assert(lua_gettop(sc->L) == 1); - sce = stat_cache_get_entry(&sc->name); - if (NULL == sce) { - lua_pop(sc->L, 1); /* pop the old function */ - break; - } - - const buffer *etag = stat_cache_etag_get(sce, etag_flags); - if (NULL == etag || !buffer_is_equal(&sc->etag, etag)) { - /* the etag is outdated, reload the function */ - lua_pop(sc->L, 1); - break; - } - - force_assert(lua_isfunction(sc->L, -1)); - - return sc->L; - } - - /* if the script was script already loaded but either got changed or - * failed to load last time */ - if (sc == NULL) { - sc = script_init(); - - if (cache->used == cache->size) { - cache->size += 16; - cache->ptr = realloc(cache->ptr, cache->size * sizeof(*(cache->ptr))); - } - - cache->ptr[cache->used++] = sc; - - buffer_copy_buffer(&sc->name, name); - - sc->L = luaL_newstate(); - luaL_openlibs(sc->L); - } - buffer_clear(&sc->etag); - - if (0 != luaL_loadfile(sc->L, name->ptr)) { - /* oops, an error, return it */ - return sc->L; - } - - sce = stat_cache_get_entry(&sc->name); - if (sce) { - const buffer *etag = stat_cache_etag_get(sce, etag_flags); - if (etag) - buffer_copy_buffer(&sc->etag, etag); - } - - force_assert(lua_isfunction(sc->L, -1)); - - return sc->L; + for (uint32_t i = 0; i < cache->used; ++i) { + script * const sc = cache->ptr[i]; + if (buffer_is_equal(&sc->name, name)) + return script_cache_check_script(sc, etag_flags); + } + return script_cache_new_script(cache, name, etag_flags); }