/** * 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: ", i); break; } case LUA_TTHREAD:{ ap_log_perror(APLOG_MARK, level, 0, r, APLOGNO(03220) "%d: ", i); break; } case LUA_TFUNCTION:{ ap_log_perror(APLOG_MARK, level, 0, r, APLOGNO(03221) "%d: ", 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) { 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; }