| /** | |
| * Licensed to the Apache Software Foundation (ASF) under one or more | |
| * contributor license agreements. See the NOTICE file distributed with | |
| * this work for additional information regarding copyright ownership. | |
| * The ASF licenses this file to You 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. | |
| */ | |
| #include "mod_lua.h" | |
| #include "http_log.h" | |
| #include "apr_uuid.h" | |
| #include "lua_config.h" | |
| #include "apr_file_info.h" | |
| #include "mod_auth.h" | |
| APLOG_USE_MODULE(lua); | |
| #ifndef AP_LUA_MODULE_EXT | |
| #if defined(NETWARE) | |
| #define AP_LUA_MODULE_EXT ".nlm" | |
| #elif defined(WIN32) | |
| #define AP_LUA_MODULE_EXT ".dll" | |
| #elif (defined(__hpux__) || defined(__hpux)) && !defined(__ia64) | |
| #define AP_LUA_MODULE_EXT ".sl" | |
| #else | |
| #define AP_LUA_MODULE_EXT ".so" | |
| #endif | |
| #endif | |
| #if APR_HAS_THREADS | |
| apr_thread_mutex_t *ap_lua_mutex; | |
| #endif | |
| extern apr_global_mutex_t *lua_ivm_mutex; | |
| void ap_lua_init_mutex(apr_pool_t *pool, server_rec *s) | |
| { | |
| apr_status_t rv; | |
| /* global IVM mutex */ | |
| rv = apr_global_mutex_child_init(&lua_ivm_mutex, | |
| apr_global_mutex_lockfile(lua_ivm_mutex), | |
| pool); | |
| if (rv != APR_SUCCESS) { | |
| ap_log_error(APLOG_MARK, APLOG_CRIT, rv, s, APLOGNO(03016) | |
| "mod_lua: Failed to reopen mutex lua-ivm-shm in child"); | |
| exit(1); /* bah :( */ | |
| } | |
| /* Server pool mutex */ | |
| #if APR_HAS_THREADS | |
| apr_thread_mutex_create(&ap_lua_mutex, APR_THREAD_MUTEX_DEFAULT, pool); | |
| #endif | |
| } | |
| /* forward dec'l from this file */ | |
| #if 0 | |
| static void pstack_dump(lua_State *L, apr_pool_t *r, int level, | |
| const char *msg) | |
| { | |
| int i; | |
| int top = lua_gettop(L); | |
| ap_log_perror(APLOG_MARK, level, 0, r, APLOGNO(03211) | |
| "Lua Stack Dump: [%s]", msg); | |
| for (i = 1; i <= top; i++) { | |
| int t = lua_type(L, i); | |
| switch (t) { | |
| case LUA_TSTRING:{ | |
| ap_log_perror(APLOG_MARK, level, 0, r, APLOGNO(03212) | |
| "%d: '%s'", i, lua_tostring(L, i)); | |
| break; | |
| } | |
| case LUA_TUSERDATA:{ | |
| ap_log_perror(APLOG_MARK, level, 0, r, APLOGNO(03213) | |
| "%d: userdata", i); | |
| break; | |
| } | |
| case LUA_TLIGHTUSERDATA:{ | |
| ap_log_perror(APLOG_MARK, level, 0, r, APLOGNO(03214) | |
| "%d: lightuserdata", i); | |
| break; | |
| } | |
| case LUA_TNIL:{ | |
| ap_log_perror(APLOG_MARK, level, 0, r, APLOGNO(03215) | |
| "%d: NIL", i); | |
| break; | |
| } | |
| case LUA_TNONE:{ | |
| ap_log_perror(APLOG_MARK, level, 0, r, APLOGNO(03216) | |
| "%d: None", i); | |
| break; | |
| } | |
| case LUA_TBOOLEAN:{ | |
| ap_log_perror(APLOG_MARK, level, 0, r, APLOGNO(03217) | |
| "%d: %s", | |
| i, lua_toboolean(L, i) ? "true" : "false"); | |
| break; | |
| } | |
| case LUA_TNUMBER:{ | |
| ap_log_perror(APLOG_MARK, level, 0, r, APLOGNO(03218) | |
| "%d: %g", i, lua_tonumber(L, i)); | |
| break; | |
| } | |
| case LUA_TTABLE:{ | |
| ap_log_perror(APLOG_MARK, level, 0, r, APLOGNO(03219) | |
| "%d: <table>", i); | |
| break; | |
| } | |
| case LUA_TTHREAD:{ | |
| ap_log_perror(APLOG_MARK, level, 0, r, APLOGNO(03220) | |
| "%d: <thread>", i); | |
| break; | |
| } | |
| case LUA_TFUNCTION:{ | |
| ap_log_perror(APLOG_MARK, level, 0, r, APLOGNO(03221) | |
| "%d: <function>", i); | |
| break; | |
| } | |
| default:{ | |
| ap_log_perror(APLOG_MARK, level, 0, r, APLOGNO(03222) | |
| "%d: unknown: [%s]", i, lua_typename(L, i)); | |
| break; | |
| } | |
| } | |
| } | |
| } | |
| #endif | |
| /* BEGIN modules*/ | |
| /* BEGIN apache lmodule */ | |
| #define makeintegerfield(L, n) lua_pushinteger(L, n); lua_setfield(L, -2, #n) | |
| void ap_lua_load_apache2_lmodule(lua_State *L) | |
| { | |
| lua_getglobal(L, "package"); | |
| lua_getfield(L, -1, "loaded"); | |
| lua_newtable(L); | |
| lua_setfield(L, -2, "apache2"); | |
| lua_setglobal(L, "apache2"); | |
| lua_pop(L, 1); /* empty stack */ | |
| lua_getglobal(L, "apache2"); | |
| lua_pushstring(L, ap_get_server_banner()); | |
| lua_setfield(L, -2, "version"); | |
| makeintegerfield(L, OK); | |
| makeintegerfield(L, DECLINED); | |
| makeintegerfield(L, DONE); | |
| makeintegerfield(L, HTTP_MOVED_TEMPORARILY); | |
| makeintegerfield(L, PROXYREQ_NONE); | |
| makeintegerfield(L, PROXYREQ_PROXY); | |
| makeintegerfield(L, PROXYREQ_REVERSE); | |
| makeintegerfield(L, PROXYREQ_RESPONSE); | |
| makeintegerfield(L, PROXYREQ_RESPONSE); | |
| makeintegerfield(L, AUTHZ_DENIED); | |
| makeintegerfield(L, AUTHZ_GRANTED); | |
| makeintegerfield(L, AUTHZ_NEUTRAL); | |
| makeintegerfield(L, AUTHZ_GENERAL_ERROR); | |
| makeintegerfield(L, AUTHZ_DENIED_NO_USER); | |
| /* | |
| makeintegerfield(L, HTTP_CONTINUE); | |
| makeintegerfield(L, HTTP_SWITCHING_PROTOCOLS); | |
| makeintegerfield(L, HTTP_PROCESSING); | |
| makeintegerfield(L, HTTP_OK); | |
| makeintegerfield(L, HTTP_CREATED); | |
| makeintegerfield(L, HTTP_ACCEPTED); | |
| makeintegerfield(L, HTTP_NON_AUTHORITATIVE); | |
| makeintegerfield(L, HTTP_NO_CONTENT); | |
| makeintegerfield(L, HTTP_RESET_CONTENT); | |
| makeintegerfield(L, HTTP_PARTIAL_CONTENT); | |
| makeintegerfield(L, HTTP_MULTI_STATUS); | |
| makeintegerfield(L, HTTP_ALREADY_REPORTED); | |
| makeintegerfield(L, HTTP_IM_USED); | |
| makeintegerfield(L, HTTP_MULTIPLE_CHOICES); | |
| makeintegerfield(L, HTTP_MOVED_PERMANENTLY); | |
| makeintegerfield(L, HTTP_MOVED_TEMPORARILY); | |
| makeintegerfield(L, HTTP_SEE_OTHER); | |
| makeintegerfield(L, HTTP_NOT_MODIFIED); | |
| makeintegerfield(L, HTTP_USE_PROXY); | |
| makeintegerfield(L, HTTP_TEMPORARY_REDIRECT); | |
| makeintegerfield(L, HTTP_PERMANENT_REDIRECT); | |
| makeintegerfield(L, HTTP_BAD_REQUEST); | |
| makeintegerfield(L, HTTP_UNAUTHORIZED); | |
| makeintegerfield(L, HTTP_PAYMENT_REQUIRED); | |
| makeintegerfield(L, HTTP_FORBIDDEN); | |
| makeintegerfield(L, HTTP_NOT_FOUND); | |
| makeintegerfield(L, HTTP_METHOD_NOT_ALLOWED); | |
| makeintegerfield(L, HTTP_NOT_ACCEPTABLE); | |
| makeintegerfield(L, HTTP_PROXY_AUTHENTICATION_REQUIRED); | |
| makeintegerfield(L, HTTP_REQUEST_TIME_OUT); | |
| makeintegerfield(L, HTTP_CONFLICT); | |
| makeintegerfield(L, HTTP_GONE); | |
| makeintegerfield(L, HTTP_LENGTH_REQUIRED); | |
| makeintegerfield(L, HTTP_PRECONDITION_FAILED); | |
| makeintegerfield(L, HTTP_REQUEST_ENTITY_TOO_LARGE); | |
| makeintegerfield(L, HTTP_REQUEST_URI_TOO_LARGE); | |
| makeintegerfield(L, HTTP_UNSUPPORTED_MEDIA_TYPE); | |
| makeintegerfield(L, HTTP_RANGE_NOT_SATISFIABLE); | |
| makeintegerfield(L, HTTP_EXPECTATION_FAILED); | |
| makeintegerfield(L, HTTP_UNPROCESSABLE_ENTITY); | |
| makeintegerfield(L, HTTP_LOCKED); | |
| makeintegerfield(L, HTTP_FAILED_DEPENDENCY); | |
| makeintegerfield(L, HTTP_UPGRADE_REQUIRED); | |
| makeintegerfield(L, HTTP_PRECONDITION_REQUIRED); | |
| makeintegerfield(L, HTTP_TOO_MANY_REQUESTS); | |
| makeintegerfield(L, HTTP_REQUEST_HEADER_FIELDS_TOO_LARGE); | |
| makeintegerfield(L, HTTP_INTERNAL_SERVER_ERROR); | |
| makeintegerfield(L, HTTP_NOT_IMPLEMENTED); | |
| makeintegerfield(L, HTTP_BAD_GATEWAY); | |
| makeintegerfield(L, HTTP_SERVICE_UNAVAILABLE); | |
| makeintegerfield(L, HTTP_GATEWAY_TIME_OUT); | |
| makeintegerfield(L, HTTP_VERSION_NOT_SUPPORTED); | |
| makeintegerfield(L, HTTP_VARIANT_ALSO_VARIES); | |
| makeintegerfield(L, HTTP_INSUFFICIENT_STORAGE); | |
| makeintegerfield(L, HTTP_LOOP_DETECTED); | |
| makeintegerfield(L, HTTP_NOT_EXTENDED); | |
| makeintegerfield(L, HTTP_NETWORK_AUTHENTICATION_REQUIRED); | |
| */ | |
| } | |
| /* END apache2 lmodule */ | |
| /* END library functions */ | |
| /* callback for cleaning up a lua vm when pool is closed */ | |
| static apr_status_t cleanup_lua(void *l) | |
| { | |
| AP_DEBUG_ASSERT(l != NULL); | |
| lua_close((lua_State *) l); | |
| return APR_SUCCESS; | |
| } | |
| static apr_status_t server_cleanup_lua(void *resource, void *params, apr_pool_t *pool) | |
| { | |
| ap_lua_server_spec* spec = (ap_lua_server_spec*) resource; | |
| AP_DEBUG_ASSERT(spec != NULL); | |
| if (spec->L != NULL) { | |
| lua_close((lua_State *) spec->L); | |
| } | |
| return APR_SUCCESS; | |
| } | |
| /* | |
| munge_path(L, | |
| "path", | |
| "?.lua", | |
| "./?.lua", | |
| lifecycle_pool, | |
| spec->package_paths, | |
| spec->file); | |
| */ | |
| /** | |
| * field -> "path" or "cpath" | |
| * sub_pat -> "?.lua" | |
| * rep_pat -> "./?.lua" | |
| * pool -> lifecycle pool for allocations | |
| * paths -> things to add | |
| * file -> ??? | |
| */ | |
| static void munge_path(lua_State *L, | |
| const char *field, | |
| const char *sub_pat, | |
| const char *rep_pat, | |
| apr_pool_t *pool, | |
| apr_array_header_t *paths, | |
| const char *file) | |
| { | |
| const char *current; | |
| const char *parent_dir; | |
| const char *pattern; | |
| const char *modified; | |
| char *part; | |
| lua_getglobal(L, "package"); | |
| lua_getfield(L, -1, field); | |
| current = lua_tostring(L, -1); | |
| parent_dir = ap_make_dirstr_parent(pool, file); | |
| pattern = apr_pstrcat(pool, parent_dir, sub_pat, NULL); | |
| luaL_gsub(L, current, rep_pat, pattern); | |
| lua_setfield(L, -3, field); | |
| lua_getfield(L, -2, field); | |
| modified = lua_tostring(L, -1); | |
| lua_pop(L, 2); | |
| part = apr_pstrcat(pool, modified, ";", apr_array_pstrcat(pool, paths, ';'), | |
| NULL); | |
| lua_pushstring(L, part); | |
| lua_setfield(L, -2, field); | |
| lua_pop(L, 1); /* pop "package" off the stack */ | |
| } | |
| #ifdef AP_ENABLE_LUAJIT | |
| static int loadjitmodule(lua_State *L, apr_pool_t *lifecycle_pool) | |
| { | |
| lua_getglobal(L, "require"); | |
| lua_pushliteral(L, "jit."); | |
| lua_pushvalue(L, -3); | |
| lua_concat(L, 2); | |
| if (lua_pcall(L, 1, 1, 0)) { | |
| const char *msg = lua_tostring(L, -1); | |
| ap_log_perror(APLOG_MARK, APLOG_DEBUG, 0, lifecycle_pool, APLOGNO(01480) | |
| "Failed to init LuaJIT: %s", msg); | |
| return 1; | |
| } | |
| lua_getfield(L, -1, "start"); | |
| lua_remove(L, -2); /* drop module table */ | |
| return 0; | |
| } | |
| #endif | |
| static apr_status_t vm_construct(lua_State **vm, void *params, apr_pool_t *lifecycle_pool) | |
| { | |
| lua_State* L; | |
| ap_lua_vm_spec *spec = params; | |
| L = luaL_newstate(); | |
| #ifdef AP_ENABLE_LUAJIT | |
| luaopen_jit(L); | |
| #endif | |
| luaL_openlibs(L); | |
| if (spec->package_paths) { | |
| munge_path(L, | |
| "path", "?.lua", "./?.lua", | |
| lifecycle_pool, | |
| spec->package_paths, | |
| spec->file); | |
| } | |
| if (spec->package_cpaths) { | |
| munge_path(L, | |
| "cpath", "?" AP_LUA_MODULE_EXT, "./?" AP_LUA_MODULE_EXT, | |
| lifecycle_pool, | |
| spec->package_cpaths, | |
| spec->file); | |
| } | |
| if (spec->cb) { | |
| spec->cb(L, lifecycle_pool, spec->cb_arg); | |
| } | |
| if (spec->bytecode && spec->bytecode_len > 0) { | |
| luaL_loadbuffer(L, spec->bytecode, spec->bytecode_len, spec->file); | |
| lua_pcall(L, 0, LUA_MULTRET, 0); | |
| } | |
| else { | |
| int rc; | |
| ap_log_perror(APLOG_MARK, APLOG_DEBUG, 0, lifecycle_pool, APLOGNO(01481) | |
| "loading lua file %s", spec->file); | |
| rc = luaL_loadfile(L, spec->file); | |
| if (rc != 0) { | |
| ap_log_perror(APLOG_MARK, APLOG_ERR, 0, lifecycle_pool, APLOGNO(01482) | |
| "Error loading %s: %s", spec->file, | |
| rc == LUA_ERRMEM ? "memory allocation error" | |
| : lua_tostring(L, 0)); | |
| return APR_EBADF; | |
| } | |
| if ( lua_pcall(L, 0, LUA_MULTRET, 0) == LUA_ERRRUN ) { | |
| ap_log_perror(APLOG_MARK, APLOG_ERR, 0, lifecycle_pool, APLOGNO(02613) | |
| "Error loading %s: %s", spec->file, | |
| lua_tostring(L, -1)); | |
| return APR_EBADF; | |
| } | |
| } | |
| #ifdef AP_ENABLE_LUAJIT | |
| loadjitmodule(L, lifecycle_pool); | |
| #endif | |
| lua_pushlightuserdata(L, lifecycle_pool); | |
| lua_setfield(L, LUA_REGISTRYINDEX, "Apache2.Wombat.pool"); | |
| *vm = L; | |
| return APR_SUCCESS; | |
| } | |
| static ap_lua_vm_spec* copy_vm_spec(apr_pool_t* pool, ap_lua_vm_spec* spec) | |
| { | |
| ap_lua_vm_spec* copied_spec = apr_pcalloc(pool, sizeof(ap_lua_vm_spec)); | |
| copied_spec->bytecode_len = spec->bytecode_len; | |
| copied_spec->bytecode = apr_pstrdup(pool, spec->bytecode); | |
| copied_spec->cb = spec->cb; | |
| copied_spec->cb_arg = NULL; | |
| copied_spec->file = apr_pstrdup(pool, spec->file); | |
| copied_spec->package_cpaths = apr_array_copy(pool, spec->package_cpaths); | |
| copied_spec->package_paths = apr_array_copy(pool, spec->package_paths); | |
| copied_spec->pool = pool; | |
| copied_spec->scope = AP_LUA_SCOPE_SERVER; | |
| copied_spec->codecache = spec->codecache; | |
| return copied_spec; | |
| } | |
| static apr_status_t server_vm_construct(lua_State **resource, void *params, apr_pool_t *pool) | |
| { | |
| lua_State* L; | |
| ap_lua_server_spec* spec = apr_pcalloc(pool, sizeof(ap_lua_server_spec)); | |
| *resource = NULL; | |
| if (vm_construct(&L, params, pool) == APR_SUCCESS) { | |
| spec->finfo = apr_pcalloc(pool, sizeof(ap_lua_finfo)); | |
| if (L != NULL) { | |
| spec->L = L; | |
| *resource = (void*) spec; | |
| lua_pushlightuserdata(L, spec); | |
| lua_setfield(L, LUA_REGISTRYINDEX, "Apache2.Lua.server_spec"); | |
| return APR_SUCCESS; | |
| } | |
| } | |
| return APR_EGENERAL; | |
| } | |
| /** | |
| * Function used to create a lua_State instance bound into the web | |
| * server in the appropriate scope. | |
| */ | |
| lua_State *ap_lua_get_lua_state(apr_pool_t *lifecycle_pool, | |
| ap_lua_vm_spec *spec, request_rec* r) | |
| { | |
| lua_State *L = NULL; | |
| ap_lua_finfo *cache_info = NULL; | |
| int tryCache = 0; | |
| if (spec->scope == AP_LUA_SCOPE_SERVER) { | |
| char *hash; | |
| apr_reslist_t* reslist = NULL; | |
| ap_lua_server_spec* sspec = NULL; | |
| hash = apr_psprintf(r->pool, "reslist:%s", spec->file); | |
| #if APR_HAS_THREADS | |
| apr_thread_mutex_lock(ap_lua_mutex); | |
| #endif | |
| if (apr_pool_userdata_get((void **)&reslist, hash, | |
| r->server->process->pool) == APR_SUCCESS) { | |
| if (reslist != NULL) { | |
| if (apr_reslist_acquire(reslist, (void**) &sspec) == APR_SUCCESS) { | |
| L = sspec->L; | |
| cache_info = sspec->finfo; | |
| } | |
| } | |
| } | |
| if (L == NULL) { | |
| ap_lua_vm_spec* server_spec = copy_vm_spec(r->server->process->pool, spec); | |
| if ( | |
| apr_reslist_create(&reslist, spec->vm_min, spec->vm_max, spec->vm_max, 0, | |
| (apr_reslist_constructor) server_vm_construct, | |
| (apr_reslist_destructor) server_cleanup_lua, | |
| server_spec, r->server->process->pool) | |
| == APR_SUCCESS && reslist != NULL) { | |
| apr_pool_userdata_set(reslist, hash, NULL, | |
| r->server->process->pool); | |
| if (apr_reslist_acquire(reslist, (void**) &sspec) == APR_SUCCESS) { | |
| L = sspec->L; | |
| cache_info = sspec->finfo; | |
| } | |
| else { | |
| #if APR_HAS_THREADS | |
| apr_thread_mutex_unlock(ap_lua_mutex); | |
| #endif | |
| return NULL; | |
| } | |
| } | |
| } | |
| #if APR_HAS_THREADS | |
| apr_thread_mutex_unlock(ap_lua_mutex); | |
| #endif | |
| } | |
| else { | |
| if (apr_pool_userdata_get((void **)&L, spec->file, | |
| lifecycle_pool) != APR_SUCCESS) { | |
| L = NULL; | |
| } | |
| } | |
| if (L == NULL) { | |
| ap_log_perror(APLOG_MARK, APLOG_DEBUG, 0, lifecycle_pool, APLOGNO(01483) | |
| "creating lua_State with file %s", spec->file); | |
| /* not available, so create */ | |
| if (!vm_construct(&L, spec, lifecycle_pool)) { | |
| AP_DEBUG_ASSERT(L != NULL); | |
| apr_pool_userdata_set(L, spec->file, cleanup_lua, lifecycle_pool); | |
| } | |
| } | |
| if (spec->codecache == AP_LUA_CACHE_FOREVER || (spec->bytecode && spec->bytecode_len > 0)) { | |
| tryCache = 1; | |
| } | |
| else { | |
| char* mkey; | |
| if (spec->scope != AP_LUA_SCOPE_SERVER) { | |
| mkey = apr_psprintf(r->pool, "ap_lua_modified:%s", spec->file); | |
| apr_pool_userdata_get((void **)&cache_info, mkey, lifecycle_pool); | |
| if (cache_info == NULL) { | |
| cache_info = apr_pcalloc(lifecycle_pool, sizeof(ap_lua_finfo)); | |
| apr_pool_userdata_set((void*) cache_info, mkey, NULL, lifecycle_pool); | |
| } | |
| } | |
| if (spec->codecache == AP_LUA_CACHE_STAT) { | |
| apr_finfo_t lua_finfo; | |
| apr_stat(&lua_finfo, spec->file, APR_FINFO_MTIME|APR_FINFO_SIZE, lifecycle_pool); | |
| /* On first visit, modified will be zero, but that's fine - The file is | |
| loaded in the vm_construct function. | |
| */ | |
| if ((cache_info->modified == lua_finfo.mtime && cache_info->size == lua_finfo.size) | |
| || cache_info->modified == 0) { | |
| tryCache = 1; | |
| } | |
| cache_info->modified = lua_finfo.mtime; | |
| cache_info->size = lua_finfo.size; | |
| } | |
| else if (spec->codecache == AP_LUA_CACHE_NEVER) { | |
| if (cache_info->runs == 0) | |
| tryCache = 1; | |
| } | |
| cache_info->runs++; | |
| } | |
| if (tryCache == 0 && spec->scope != AP_LUA_SCOPE_ONCE) { | |
| int rc; | |
| ap_log_perror(APLOG_MARK, APLOG_DEBUG, 0, lifecycle_pool, APLOGNO(02332) | |
| "(re)loading lua file %s", spec->file); | |
| rc = luaL_loadfile(L, spec->file); | |
| if (rc != 0) { | |
| ap_log_perror(APLOG_MARK, APLOG_ERR, 0, lifecycle_pool, APLOGNO(02333) | |
| "Error loading %s: %s", spec->file, | |
| rc == LUA_ERRMEM ? "memory allocation error" | |
| : lua_tostring(L, 0)); | |
| return 0; | |
| } | |
| lua_pcall(L, 0, LUA_MULTRET, 0); | |
| } | |
| return L; | |
| } |