| /* |
| * Lua unsafe core engine |
| * |
| * Copyright 2015-2016 Thierry Fournier <tfournier@arpalert.org> |
| * |
| * 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 (at your option) any later version. |
| * |
| */ |
| |
| #define _GNU_SOURCE |
| |
| #include <ctype.h> |
| #include <setjmp.h> |
| |
| #include <lauxlib.h> |
| #include <lua.h> |
| #include <lualib.h> |
| |
| #if !defined(LUA_VERSION_NUM) || LUA_VERSION_NUM < 503 |
| #error "Requires Lua 5.3 or later." |
| #endif |
| |
| #include <import/ebpttree.h> |
| |
| #include <haproxy/api.h> |
| #include <haproxy/applet.h> |
| #include <haproxy/arg.h> |
| #include <haproxy/auth.h> |
| #include <haproxy/cfgparse.h> |
| #include <haproxy/channel.h> |
| #include <haproxy/cli.h> |
| #include <haproxy/clock.h> |
| #include <haproxy/connection.h> |
| #include <haproxy/filters.h> |
| #include <haproxy/h1.h> |
| #include <haproxy/hlua.h> |
| #include <haproxy/hlua_fcn.h> |
| #include <haproxy/http_ana.h> |
| #include <haproxy/http_client.h> |
| #include <haproxy/http_fetch.h> |
| #include <haproxy/http_htx.h> |
| #include <haproxy/http_rules.h> |
| #include <haproxy/log.h> |
| #include <haproxy/map.h> |
| #include <haproxy/obj_type.h> |
| #include <haproxy/pattern.h> |
| #include <haproxy/payload.h> |
| #include <haproxy/proxy.h> |
| #include <haproxy/regex.h> |
| #include <haproxy/sample.h> |
| #include <haproxy/sc_strm.h> |
| #include <haproxy/server.h> |
| #include <haproxy/session.h> |
| #include <haproxy/ssl_ckch.h> |
| #include <haproxy/ssl_sock.h> |
| #include <haproxy/stats-t.h> |
| #include <haproxy/stconn.h> |
| #include <haproxy/stream.h> |
| #include <haproxy/task.h> |
| #include <haproxy/tcp_rules.h> |
| #include <haproxy/thread.h> |
| #include <haproxy/tools.h> |
| #include <haproxy/vars.h> |
| #include <haproxy/xref.h> |
| #include <haproxy/event_hdl.h> |
| #include <haproxy/check.h> |
| |
| /* Lua uses longjmp to perform yield or throwing errors. This |
| * macro is used only for identifying the function that can |
| * not return because a longjmp is executed. |
| * __LJMP marks a prototype of hlua file that can use longjmp. |
| * WILL_LJMP() marks an lua function that will use longjmp. |
| * MAY_LJMP() marks an lua function that may use longjmp. |
| */ |
| #define __LJMP |
| #define WILL_LJMP(func) do { func; my_unreachable(); } while(0) |
| #define MAY_LJMP(func) func |
| |
| /* This couple of function executes securely some Lua calls outside of |
| * the lua runtime environment. Each Lua call can return a longjmp |
| * if it encounter a memory error. |
| * |
| * Lua documentation extract: |
| * |
| * If an error happens outside any protected environment, Lua calls |
| * a panic function (see lua_atpanic) and then calls abort, thus |
| * exiting the host application. Your panic function can avoid this |
| * exit by never returning (e.g., doing a long jump to your own |
| * recovery point outside Lua). |
| * |
| * The panic function runs as if it were a message handler (see |
| * #2.3); in particular, the error message is at the top of the |
| * stack. However, there is no guarantee about stack space. To push |
| * anything on the stack, the panic function must first check the |
| * available space (see #4.2). |
| * |
| * We must check all the Lua entry point. This includes: |
| * - The include/proto/hlua.h exported functions |
| * - the task wrapper function |
| * - The action wrapper function |
| * - The converters wrapper function |
| * - The sample-fetch wrapper functions |
| * |
| * It is tolerated that the initialisation function returns an abort. |
| * Before each Lua abort, an error message is written on stderr. |
| * |
| * The macro SET_SAFE_LJMP initialise the longjmp. The Macro |
| * RESET_SAFE_LJMP reset the longjmp. These function must be macro |
| * because they must be exists in the program stack when the longjmp |
| * is called. |
| * |
| * Note that the Lua processing is not really thread safe. It provides |
| * heavy system which consists to add our own lock function in the Lua |
| * code and recompile the library. This system will probably not accepted |
| * by maintainers of various distribs. |
| * |
| * Our main execution point of the Lua is the function lua_resume(). A |
| * quick looking on the Lua sources displays a lua_lock() a the start |
| * of function and a lua_unlock() at the end of the function. So I |
| * conclude that the Lua thread safe mode just perform a mutex around |
| * all execution. So I prefer to do this in the HAProxy code, it will be |
| * easier for distro maintainers. |
| * |
| * Note that the HAProxy lua functions rounded by the macro SET_SAFE_LJMP |
| * and RESET_SAFE_LJMP manipulates the Lua stack, so it will be careful |
| * to set mutex around these functions. |
| */ |
| __decl_spinlock(hlua_global_lock); |
| THREAD_LOCAL jmp_buf safe_ljmp_env; |
| static int hlua_panic_safe(lua_State *L) { return 0; } |
| static int hlua_panic_ljmp(lua_State *L) { WILL_LJMP(longjmp(safe_ljmp_env, 1)); return 0; } |
| |
| /* This is the chained list of struct hlua_function referenced |
| * for haproxy action, sample-fetches, converters, cli and |
| * applet bindings. It is used for a post-initialisation control. |
| */ |
| static struct list referenced_functions = LIST_HEAD_INIT(referenced_functions); |
| |
| /* This variable is used only during initialization to identify the Lua state |
| * currently being initialized. 0 is the common lua state, 1 to n are the Lua |
| * states dedicated to each thread (in this case hlua_state_id==tid+1). |
| */ |
| static int hlua_state_id; |
| |
| /* This is a NULL-terminated list of lua file which are referenced to load per thread */ |
| static char ***per_thread_load = NULL; |
| |
| lua_State *hlua_init_state(int thread_id); |
| |
| /* This function takes the Lua global lock. Keep this function's visibility |
| * global so that it can appear in stack dumps and performance profiles! |
| */ |
| static inline void lua_take_global_lock() |
| { |
| HA_SPIN_LOCK(LUA_LOCK, &hlua_global_lock); |
| } |
| |
| static inline void lua_drop_global_lock() |
| { |
| HA_SPIN_UNLOCK(LUA_LOCK, &hlua_global_lock); |
| } |
| |
| /* lua lock helpers: only lock when required |
| * |
| * state_id == 0: we're operating on the main lua stack (shared between |
| * os threads), so we need to acquire the main lock |
| * |
| * If the thread already owns the lock (_hlua_locked != 0), skip the lock |
| * attempt. This could happen if we run under protected lua environment. |
| * Not doing this could result in deadlocks because of nested locking |
| * attempts from the same thread |
| */ |
| static THREAD_LOCAL int _hlua_locked = 0; |
| static inline void hlua_lock(struct hlua *hlua) |
| { |
| if (hlua->state_id != 0) |
| return; |
| if (!_hlua_locked) |
| lua_take_global_lock(); |
| _hlua_locked += 1; |
| } |
| static inline void hlua_unlock(struct hlua *hlua) |
| { |
| if (hlua->state_id != 0) |
| return; |
| BUG_ON(_hlua_locked <= 0); |
| _hlua_locked--; |
| /* drop the lock once the lock count reaches 0 */ |
| if (!_hlua_locked) |
| lua_drop_global_lock(); |
| } |
| |
| #define SET_SAFE_LJMP_L(__L, __HLUA) \ |
| ({ \ |
| int ret; \ |
| hlua_lock(__HLUA); \ |
| if (setjmp(safe_ljmp_env) != 0) { \ |
| lua_atpanic(__L, hlua_panic_safe); \ |
| ret = 0; \ |
| hlua_unlock(__HLUA); \ |
| } else { \ |
| lua_atpanic(__L, hlua_panic_ljmp); \ |
| ret = 1; \ |
| } \ |
| ret; \ |
| }) |
| |
| /* If we are the last function catching Lua errors, we |
| * must reset the panic function. |
| */ |
| #define RESET_SAFE_LJMP_L(__L, __HLUA) \ |
| do { \ |
| lua_atpanic(__L, hlua_panic_safe); \ |
| hlua_unlock(__HLUA); \ |
| } while(0) |
| |
| #define SET_SAFE_LJMP(__HLUA) \ |
| SET_SAFE_LJMP_L((__HLUA)->T, __HLUA) |
| |
| #define RESET_SAFE_LJMP(__HLUA) \ |
| RESET_SAFE_LJMP_L((__HLUA)->T, __HLUA) |
| |
| #define SET_SAFE_LJMP_PARENT(__HLUA) \ |
| SET_SAFE_LJMP_L(hlua_states[(__HLUA)->state_id], __HLUA) |
| |
| #define RESET_SAFE_LJMP_PARENT(__HLUA) \ |
| RESET_SAFE_LJMP_L(hlua_states[(__HLUA)->state_id], __HLUA) |
| |
| /* Applet status flags */ |
| #define APPLET_DONE 0x01 /* applet processing is done. */ |
| /* unused: 0x02 */ |
| #define APPLET_HDR_SENT 0x04 /* Response header sent. */ |
| /* unused: 0x08, 0x10 */ |
| #define APPLET_HTTP11 0x20 /* Last chunk sent. */ |
| #define APPLET_RSP_SENT 0x40 /* The response was fully sent */ |
| |
| /* The main Lua execution context. The 0 index is the |
| * common state shared by all threads. |
| */ |
| static lua_State *hlua_states[MAX_THREADS + 1]; |
| |
| #define HLUA_FLT_CB_FINAL 0x00000001 |
| #define HLUA_FLT_CB_RETVAL 0x00000002 |
| #define HLUA_FLT_CB_ARG_CHN 0x00000004 |
| #define HLUA_FLT_CB_ARG_HTTP_MSG 0x00000008 |
| |
| #define HLUA_FLT_CTX_FL_PAYLOAD 0x00000001 |
| |
| struct hlua_reg_filter { |
| char *name; |
| int flt_ref[MAX_THREADS + 1]; |
| int fun_ref[MAX_THREADS + 1]; |
| struct list l; |
| }; |
| |
| struct hlua_flt_config { |
| struct hlua_reg_filter *reg; |
| int ref[MAX_THREADS + 1]; |
| char **args; |
| }; |
| |
| struct hlua_flt_ctx { |
| int ref; /* ref to the filter lua object */ |
| struct hlua *hlua[2]; /* lua runtime context (0: request, 1: response) */ |
| unsigned int cur_off[2]; /* current offset (0: request, 1: response) */ |
| unsigned int cur_len[2]; /* current forwardable length (0: request, 1: response) */ |
| unsigned int flags; /* HLUA_FLT_CTX_FL_* */ |
| }; |
| |
| /* appctx context used by the cosockets */ |
| struct hlua_csk_ctx { |
| int connected; |
| struct xref xref; /* cross reference with the Lua object owner. */ |
| struct list wake_on_read; |
| struct list wake_on_write; |
| struct appctx *appctx; |
| int die; |
| }; |
| |
| /* appctx context used by TCP services */ |
| struct hlua_tcp_ctx { |
| struct hlua *hlua; |
| int flags; |
| struct task *task; |
| }; |
| |
| /* appctx context used by HTTP services */ |
| struct hlua_http_ctx { |
| struct hlua *hlua; |
| int left_bytes; /* The max amount of bytes that we can read. */ |
| int flags; |
| int status; |
| const char *reason; |
| struct task *task; |
| }; |
| |
| /* used by registered CLI keywords */ |
| struct hlua_cli_ctx { |
| struct hlua *hlua; |
| struct task *task; |
| struct hlua_function *fcn; |
| }; |
| |
| DECLARE_STATIC_POOL(pool_head_hlua_flt_ctx, "hlua_flt_ctx", sizeof(struct hlua_flt_ctx)); |
| |
| static int hlua_filter_from_payload(struct filter *filter); |
| |
| /* This is the chained list of struct hlua_flt referenced |
| * for haproxy filters. It is used for a post-initialisation control. |
| */ |
| static struct list referenced_filters = LIST_HEAD_INIT(referenced_filters); |
| |
| |
| /* This is the memory pool containing struct lua for applets |
| * (including cli). |
| */ |
| DECLARE_STATIC_POOL(pool_head_hlua, "hlua", sizeof(struct hlua)); |
| |
| /* Used for Socket connection. */ |
| static struct proxy *socket_proxy; |
| static struct server *socket_tcp; |
| #ifdef USE_OPENSSL |
| static struct server *socket_ssl; |
| #endif |
| |
| /* List head of the function called at the initialisation time. */ |
| struct list hlua_init_functions[MAX_THREADS + 1]; |
| |
| /* The following variables contains the reference of the different |
| * Lua classes. These references are useful for identify metadata |
| * associated with an object. |
| */ |
| static int class_txn_ref; |
| static int class_socket_ref; |
| static int class_channel_ref; |
| static int class_fetches_ref; |
| static int class_converters_ref; |
| static int class_http_ref; |
| static int class_http_msg_ref; |
| static int class_httpclient_ref; |
| static int class_map_ref; |
| static int class_applet_tcp_ref; |
| static int class_applet_http_ref; |
| static int class_txn_reply_ref; |
| |
| /* Lua max execution timeouts. By default, stream-related |
| * lua coroutines (e.g.: actions) have a short timeout. |
| * On the other hand tasks coroutines don't have a timeout because |
| * a task may remain alive during all the haproxy execution. |
| * |
| * Timeouts are expressed in milliseconds, they are meant to be used |
| * with hlua timer's API exclusively. |
| * 0 means no timeout |
| */ |
| static uint32_t hlua_timeout_burst = 1000; /* burst timeout. */ |
| static uint32_t hlua_timeout_session = 4000; /* session timeout. */ |
| static uint32_t hlua_timeout_task = 0; /* task timeout. */ |
| static uint32_t hlua_timeout_applet = 4000; /* applet timeout. */ |
| |
| /* hlua multipurpose timer: |
| * used to compute burst lua time (within a single hlua_ctx_resume()) |
| * and cumulative lua time for a given coroutine, and to check |
| * the lua coroutine against the configured timeouts |
| */ |
| |
| /* fetch per-thread cpu_time with ms precision (may wrap) */ |
| static inline uint32_t _hlua_time_ms() |
| { |
| /* We're interested in the current cpu time in ms, which will be returned |
| * as a uint32_t to save some space. |
| * We must take the following into account: |
| * |
| * - now_cpu_time_fast() which returns the time in nanoseconds as a uint64_t |
| * will wrap every 585 years. |
| * - uint32_t may only contain 4294967295ms (~=49.7 days), so _hlua_time_ms() |
| * itself will also wrap every 49.7 days. |
| * |
| * While we can safely ignore the now_cpu_time_fast() wrap, we must |
| * take care of the uint32_t wrap by making sure to exclusively |
| * manipulate the time using uint32_t everywhere _hlua_time_ms() |
| * is involved. |
| */ |
| return (uint32_t)(now_cpu_time_fast() / 1000000ULL); |
| } |
| |
| /* computes time spent in a single lua execution (in ms) */ |
| static inline uint32_t _hlua_time_burst(const struct hlua_timer *timer) |
| { |
| uint32_t burst_ms; |
| |
| /* wrapping is expected and properly |
| * handled thanks to _hlua_time_ms() and burst_ms |
| * being of the same type |
| */ |
| burst_ms = _hlua_time_ms() - timer->start; |
| return burst_ms; |
| } |
| |
| static inline void hlua_timer_init(struct hlua_timer *timer, unsigned int max) |
| { |
| timer->cumulative = 0; |
| timer->burst = 0; |
| timer->max = max; |
| } |
| |
| /* reset the timer ctx between 2 yields */ |
| static inline void hlua_timer_reset(struct hlua_timer *timer) |
| { |
| timer->cumulative += timer->burst; |
| timer->burst = 0; |
| } |
| |
| /* start the timer right before a new execution */ |
| static inline void hlua_timer_start(struct hlua_timer *timer) |
| { |
| timer->start = _hlua_time_ms(); |
| } |
| |
| /* update hlua timer when finishing an execution */ |
| static inline void hlua_timer_stop(struct hlua_timer *timer) |
| { |
| timer->burst += _hlua_time_burst(timer); |
| } |
| |
| /* check the timers for current hlua context: |
| * - first check for burst timeout (max execution time for the current |
| hlua resume, ie: time between effective yields) |
| * - then check for yield cumulative timeout |
| * |
| * Returns 1 if the check succeeded and 0 if it failed |
| * (ie: timeout exceeded) |
| */ |
| static inline int hlua_timer_check(const struct hlua_timer *timer) |
| { |
| uint32_t pburst = _hlua_time_burst(timer); /* pending burst time in ms */ |
| |
| if (hlua_timeout_burst && (timer->burst + pburst) > hlua_timeout_burst) |
| return 0; /* burst timeout exceeded */ |
| if (timer->max && (timer->cumulative + timer->burst + pburst) > timer->max) |
| return 0; /* cumulative timeout exceeded */ |
| return 1; /* ok */ |
| } |
| |
| /* Interrupts the Lua processing each "hlua_nb_instruction" instructions. |
| * it is used for preventing infinite loops. |
| * |
| * I test the scheer with an infinite loop containing one incrementation |
| * and one test. I run this loop between 10 seconds, I raise a ceil of |
| * 710M loops from one interrupt each 9000 instructions, so I fix the value |
| * to one interrupt each 10 000 instructions. |
| * |
| * configured | Number of |
| * instructions | loops executed |
| * between two | in milions |
| * forced yields | |
| * ---------------+--------------- |
| * 10 | 160 |
| * 500 | 670 |
| * 1000 | 680 |
| * 5000 | 700 |
| * 7000 | 700 |
| * 8000 | 700 |
| * 9000 | 710 <- ceil |
| * 10000 | 710 |
| * 100000 | 710 |
| * 1000000 | 710 |
| * |
| */ |
| static unsigned int hlua_nb_instruction = 10000; |
| |
| /* Descriptor for the memory allocation state. The limit is pre-initialised to |
| * 0 until it is replaced by "tune.lua.maxmem" during the config parsing, or it |
| * is replaced with ~0 during post_init after everything was loaded. This way |
| * it is guaranteed that if limit is ~0 the boot is complete and that if it's |
| * zero it's not yet limited and proper accounting is required. |
| */ |
| struct hlua_mem_allocator { |
| size_t allocated; |
| size_t limit; |
| }; |
| |
| static struct hlua_mem_allocator hlua_global_allocator THREAD_ALIGNED(64); |
| |
| /* hlua event subscription */ |
| struct hlua_event_sub { |
| int fcn_ref; |
| int state_id; |
| struct hlua *hlua; |
| struct task *task; |
| event_hdl_async_equeue equeue; |
| struct event_hdl_sub *sub; |
| uint8_t paused; |
| }; |
| |
| /* This is the memory pool containing struct hlua_event_sub |
| * for event subscriptions from lua |
| */ |
| DECLARE_STATIC_POOL(pool_head_hlua_event_sub, "hlua_esub", sizeof(struct hlua_event_sub)); |
| |
| /* These functions converts types between HAProxy internal args or |
| * sample and LUA types. Another function permits to check if the |
| * LUA stack contains arguments according with an required ARG_T |
| * format. |
| */ |
| static int hlua_arg2lua(lua_State *L, const struct arg *arg); |
| static int hlua_lua2arg(lua_State *L, int ud, struct arg *arg); |
| __LJMP static int hlua_lua2arg_check(lua_State *L, int first, struct arg *argp, |
| uint64_t mask, struct proxy *p); |
| static int hlua_smp2lua(lua_State *L, struct sample *smp); |
| static int hlua_smp2lua_str(lua_State *L, struct sample *smp); |
| static int hlua_lua2smp(lua_State *L, int ud, struct sample *smp); |
| |
| __LJMP static int hlua_http_get_headers(lua_State *L, struct http_msg *msg); |
| |
| struct prepend_path { |
| struct list l; |
| char *type; |
| char *path; |
| }; |
| |
| static struct list prepend_path_list = LIST_HEAD_INIT(prepend_path_list); |
| |
| #define SEND_ERR(__be, __fmt, __args...) \ |
| do { \ |
| send_log(__be, LOG_ERR, __fmt, ## __args); \ |
| if (!(global.mode & MODE_QUIET) || (global.mode & MODE_VERBOSE)) \ |
| ha_alert(__fmt, ## __args); \ |
| } while (0) |
| |
| static inline struct hlua_function *new_hlua_function() |
| { |
| struct hlua_function *fcn; |
| int i; |
| |
| fcn = calloc(1, sizeof(*fcn)); |
| if (!fcn) |
| return NULL; |
| LIST_APPEND(&referenced_functions, &fcn->l); |
| for (i = 0; i < MAX_THREADS + 1; i++) |
| fcn->function_ref[i] = -1; |
| return fcn; |
| } |
| |
| static inline void release_hlua_function(struct hlua_function *fcn) |
| { |
| if (!fcn) |
| return; |
| if (fcn->name) |
| ha_free(&fcn->name); |
| LIST_DELETE(&fcn->l); |
| ha_free(&fcn); |
| } |
| |
| /* If the common state is set, the stack id is 0, otherwise it is the tid + 1 */ |
| static inline int fcn_ref_to_stack_id(struct hlua_function *fcn) |
| { |
| if (fcn->function_ref[0] == -1) |
| return tid + 1; |
| return 0; |
| } |
| |
| /* Create a new registered filter. Only its name is filled */ |
| static inline struct hlua_reg_filter *new_hlua_reg_filter(const char *name) |
| { |
| struct hlua_reg_filter *reg_flt; |
| int i; |
| |
| reg_flt = calloc(1, sizeof(*reg_flt)); |
| if (!reg_flt) |
| return NULL; |
| reg_flt->name = strdup(name); |
| if (!reg_flt->name) { |
| free(reg_flt); |
| return NULL; |
| } |
| LIST_APPEND(&referenced_filters, ®_flt->l); |
| for (i = 0; i < MAX_THREADS + 1; i++) { |
| reg_flt->flt_ref[i] = -1; |
| reg_flt->fun_ref[i] = -1; |
| } |
| return reg_flt; |
| } |
| |
| /* Release a registered filter */ |
| static inline void release_hlua_reg_filter(struct hlua_reg_filter *reg_flt) |
| { |
| if (!reg_flt) |
| return; |
| if (reg_flt->name) |
| ha_free(®_flt->name); |
| LIST_DELETE(®_flt->l); |
| ha_free(®_flt); |
| } |
| |
| /* If the common state is set, the stack id is 0, otherwise it is the tid + 1 */ |
| static inline int reg_flt_to_stack_id(struct hlua_reg_filter *reg_flt) |
| { |
| if (reg_flt->fun_ref[0] == -1) |
| return tid + 1; |
| return 0; |
| } |
| |
| /* Used to check an Lua function type in the stack. It creates and |
| * returns a reference of the function. This function throws an |
| * error if the argument is not a "function". |
| * When no longer used, the ref must be released with hlua_unref() |
| */ |
| __LJMP int hlua_checkfunction(lua_State *L, int argno) |
| { |
| if (!lua_isfunction(L, argno)) { |
| const char *msg = lua_pushfstring(L, "function expected, got %s", luaL_typename(L, argno)); |
| WILL_LJMP(luaL_argerror(L, argno, msg)); |
| } |
| lua_pushvalue(L, argno); |
| return luaL_ref(L, LUA_REGISTRYINDEX); |
| } |
| |
| /* Used to check an Lua table type in the stack. It creates and |
| * returns a reference of the table. This function throws an |
| * error if the argument is not a "table". |
| * When no longer used, the ref must be released with hlua_unref() |
| */ |
| __LJMP int hlua_checktable(lua_State *L, int argno) |
| { |
| if (!lua_istable(L, argno)) { |
| const char *msg = lua_pushfstring(L, "table expected, got %s", luaL_typename(L, argno)); |
| WILL_LJMP(luaL_argerror(L, argno, msg)); |
| } |
| lua_pushvalue(L, argno); |
| return luaL_ref(L, LUA_REGISTRYINDEX); |
| } |
| |
| /* Get a reference to the object that is at the top of the stack |
| * The referenced object will be popped from the stack |
| * |
| * The function returns the reference to the object which must |
| * be cleared using hlua_unref() when no longer used |
| */ |
| __LJMP int hlua_ref(lua_State *L) |
| { |
| return MAY_LJMP(luaL_ref(L, LUA_REGISTRYINDEX)); |
| } |
| |
| /* Pushes a reference previously created using luaL_ref(L, LUA_REGISTRYINDEX) |
| * on <L> stack |
| * (ie: hlua_checkfunction(), hlua_checktable() or hlua_ref()) |
| * |
| * When the reference is no longer used, it should be released by calling |
| * hlua_unref() |
| * |
| * <L> can be from any co-routine as long as it belongs to the same lua |
| * parent state that the one used to get the reference. |
| */ |
| void hlua_pushref(lua_State *L, int ref) |
| { |
| lua_rawgeti(L, LUA_REGISTRYINDEX, ref); |
| } |
| |
| /* Releases a reference previously created using luaL_ref(L, LUA_REGISTRYINDEX) |
| * (ie: hlua_checkfunction(), hlua_checktable() or hlua_ref()) |
| * |
| * This will allow the reference to be reused and the referred object |
| * to be garbage collected. |
| * |
| * <L> can be from any co-routine as long as it belongs to the same lua |
| * parent state that the one used to get the reference. |
| */ |
| void hlua_unref(lua_State *L, int ref) |
| { |
| luaL_unref(L, LUA_REGISTRYINDEX, ref); |
| } |
| |
| __LJMP const char *hlua_traceback(lua_State *L, const char* sep) |
| { |
| lua_Debug ar; |
| int level = 0; |
| struct buffer *msg = get_trash_chunk(); |
| |
| while (lua_getstack(L, level++, &ar)) { |
| /* Fill fields: |
| * 'S': fills in the fields source, short_src, linedefined, lastlinedefined, and what; |
| * 'l': fills in the field currentline; |
| * 'n': fills in the field name and namewhat; |
| * 't': fills in the field istailcall; |
| */ |
| lua_getinfo(L, "Slnt", &ar); |
| |
| /* skip these empty entries, usually they come from deep C functions */ |
| if (ar.currentline < 0 && *ar.what == 'C' && !*ar.namewhat && !ar.name) |
| continue; |
| |
| /* Add separator */ |
| if (b_data(msg)) |
| chunk_appendf(msg, "%s", sep); |
| |
| /* Append code localisation */ |
| if (ar.currentline > 0) |
| chunk_appendf(msg, "%s:%d: ", ar.short_src, ar.currentline); |
| else |
| chunk_appendf(msg, "%s: ", ar.short_src); |
| |
| /* |
| * Get function name |
| * |
| * if namewhat is no empty, name is defined. |
| * what contains "Lua" for Lua function, "C" for C function, |
| * or "main" for main code. |
| */ |
| if (*ar.namewhat != '\0' && ar.name != NULL) /* is there a name from code? */ |
| chunk_appendf(msg, "in %s '%s'", ar.namewhat, ar.name); /* use it */ |
| |
| else if (*ar.what == 'm') /* "main", the code is not executed in a function */ |
| chunk_appendf(msg, "in main chunk"); |
| |
| else if (*ar.what != 'C') /* for Lua functions, use <file:line> */ |
| chunk_appendf(msg, "in function line %d", ar.linedefined); |
| |
| else /* nothing left... */ |
| chunk_appendf(msg, "?"); |
| |
| |
| /* Display tailed call */ |
| if (ar.istailcall) |
| chunk_appendf(msg, " ..."); |
| } |
| |
| return msg->area; |
| } |
| |
| |
| /* This function check the number of arguments available in the |
| * stack. If the number of arguments available is not the same |
| * then <nb> an error is thrown. |
| */ |
| __LJMP static inline void check_args(lua_State *L, int nb, char *fcn) |
| { |
| if (lua_gettop(L) == nb) |
| return; |
| WILL_LJMP(luaL_error(L, "'%s' needs %d arguments", fcn, nb)); |
| } |
| |
| /* This function pushes an error string prefixed by the file name |
| * and the line number where the error is encountered. |
| */ |
| static int hlua_pusherror(lua_State *L, const char *fmt, ...) |
| { |
| va_list argp; |
| va_start(argp, fmt); |
| luaL_where(L, 1); |
| lua_pushvfstring(L, fmt, argp); |
| va_end(argp); |
| lua_concat(L, 2); |
| return 1; |
| } |
| |
| /* This functions is used with sample fetch and converters. It |
| * converts the HAProxy configuration argument in a lua stack |
| * values. |
| * |
| * It takes an array of "arg", and each entry of the array is |
| * converted and pushed in the LUA stack. |
| */ |
| static int hlua_arg2lua(lua_State *L, const struct arg *arg) |
| { |
| switch (arg->type) { |
| case ARGT_SINT: |
| case ARGT_TIME: |
| case ARGT_SIZE: |
| lua_pushinteger(L, arg->data.sint); |
| break; |
| |
| case ARGT_STR: |
| lua_pushlstring(L, arg->data.str.area, arg->data.str.data); |
| break; |
| |
| case ARGT_IPV4: |
| case ARGT_IPV6: |
| case ARGT_MSK4: |
| case ARGT_MSK6: |
| case ARGT_FE: |
| case ARGT_BE: |
| case ARGT_TAB: |
| case ARGT_SRV: |
| case ARGT_USR: |
| case ARGT_MAP: |
| default: |
| lua_pushnil(L); |
| break; |
| } |
| return 1; |
| } |
| |
| /* This function take one entry in an LUA stack at the index "ud", |
| * and try to convert it in an HAProxy argument entry. This is useful |
| * with sample fetch wrappers. The input arguments are given to the |
| * lua wrapper and converted as arg list by the function. |
| */ |
| static int hlua_lua2arg(lua_State *L, int ud, struct arg *arg) |
| { |
| switch (lua_type(L, ud)) { |
| |
| case LUA_TNUMBER: |
| case LUA_TBOOLEAN: |
| arg->type = ARGT_SINT; |
| arg->data.sint = lua_tointeger(L, ud); |
| break; |
| |
| case LUA_TSTRING: |
| arg->type = ARGT_STR; |
| arg->data.str.area = (char *)lua_tolstring(L, ud, &arg->data.str.data); |
| /* We don't know the actual size of the underlying allocation, so be conservative. */ |
| arg->data.str.size = arg->data.str.data+1; /* count the terminating null byte */ |
| arg->data.str.head = 0; |
| break; |
| |
| case LUA_TUSERDATA: |
| case LUA_TNIL: |
| case LUA_TTABLE: |
| case LUA_TFUNCTION: |
| case LUA_TTHREAD: |
| case LUA_TLIGHTUSERDATA: |
| arg->type = ARGT_SINT; |
| arg->data.sint = 0; |
| break; |
| } |
| return 1; |
| } |
| |
| /* the following functions are used to convert a struct sample |
| * in Lua type. This useful to convert the return of the |
| * fetches or converters. |
| */ |
| static int hlua_smp2lua(lua_State *L, struct sample *smp) |
| { |
| switch (smp->data.type) { |
| case SMP_T_SINT: |
| case SMP_T_BOOL: |
| lua_pushinteger(L, smp->data.u.sint); |
| break; |
| |
| case SMP_T_BIN: |
| case SMP_T_STR: |
| lua_pushlstring(L, smp->data.u.str.area, smp->data.u.str.data); |
| break; |
| |
| case SMP_T_METH: |
| switch (smp->data.u.meth.meth) { |
| case HTTP_METH_OPTIONS: lua_pushstring(L, "OPTIONS"); break; |
| case HTTP_METH_GET: lua_pushstring(L, "GET"); break; |
| case HTTP_METH_HEAD: lua_pushstring(L, "HEAD"); break; |
| case HTTP_METH_POST: lua_pushstring(L, "POST"); break; |
| case HTTP_METH_PUT: lua_pushstring(L, "PUT"); break; |
| case HTTP_METH_DELETE: lua_pushstring(L, "DELETE"); break; |
| case HTTP_METH_TRACE: lua_pushstring(L, "TRACE"); break; |
| case HTTP_METH_CONNECT: lua_pushstring(L, "CONNECT"); break; |
| case HTTP_METH_OTHER: |
| lua_pushlstring(L, smp->data.u.meth.str.area, smp->data.u.meth.str.data); |
| break; |
| default: |
| lua_pushnil(L); |
| break; |
| } |
| break; |
| |
| case SMP_T_IPV4: |
| case SMP_T_IPV6: |
| case SMP_T_ADDR: /* This type is never used to qualify a sample. */ |
| if (sample_casts[smp->data.type][SMP_T_STR] && |
| sample_casts[smp->data.type][SMP_T_STR](smp)) |
| lua_pushlstring(L, smp->data.u.str.area, smp->data.u.str.data); |
| else |
| lua_pushnil(L); |
| break; |
| default: |
| lua_pushnil(L); |
| break; |
| } |
| return 1; |
| } |
| |
| /* the following functions are used to convert a struct sample |
| * in Lua strings. This is useful to convert the return of the |
| * fetches or converters. |
| */ |
| static int hlua_smp2lua_str(lua_State *L, struct sample *smp) |
| { |
| switch (smp->data.type) { |
| |
| case SMP_T_BIN: |
| case SMP_T_STR: |
| lua_pushlstring(L, smp->data.u.str.area, smp->data.u.str.data); |
| break; |
| |
| case SMP_T_METH: |
| switch (smp->data.u.meth.meth) { |
| case HTTP_METH_OPTIONS: lua_pushstring(L, "OPTIONS"); break; |
| case HTTP_METH_GET: lua_pushstring(L, "GET"); break; |
| case HTTP_METH_HEAD: lua_pushstring(L, "HEAD"); break; |
| case HTTP_METH_POST: lua_pushstring(L, "POST"); break; |
| case HTTP_METH_PUT: lua_pushstring(L, "PUT"); break; |
| case HTTP_METH_DELETE: lua_pushstring(L, "DELETE"); break; |
| case HTTP_METH_TRACE: lua_pushstring(L, "TRACE"); break; |
| case HTTP_METH_CONNECT: lua_pushstring(L, "CONNECT"); break; |
| case HTTP_METH_OTHER: |
| lua_pushlstring(L, smp->data.u.meth.str.area, smp->data.u.meth.str.data); |
| break; |
| default: |
| lua_pushstring(L, ""); |
| break; |
| } |
| break; |
| |
| case SMP_T_SINT: |
| case SMP_T_BOOL: |
| case SMP_T_IPV4: |
| case SMP_T_IPV6: |
| case SMP_T_ADDR: /* This type is never used to qualify a sample. */ |
| if (sample_casts[smp->data.type][SMP_T_STR] && |
| sample_casts[smp->data.type][SMP_T_STR](smp)) |
| lua_pushlstring(L, smp->data.u.str.area, smp->data.u.str.data); |
| else |
| lua_pushstring(L, ""); |
| break; |
| default: |
| lua_pushstring(L, ""); |
| break; |
| } |
| return 1; |
| } |
| |
| /* the following functions are used to convert an Lua type in a |
| * struct sample. This is useful to provide data from a converter |
| * to the LUA code. |
| */ |
| static int hlua_lua2smp(lua_State *L, int ud, struct sample *smp) |
| { |
| switch (lua_type(L, ud)) { |
| |
| case LUA_TNUMBER: |
| smp->data.type = SMP_T_SINT; |
| smp->data.u.sint = lua_tointeger(L, ud); |
| break; |
| |
| |
| case LUA_TBOOLEAN: |
| smp->data.type = SMP_T_BOOL; |
| smp->data.u.sint = lua_toboolean(L, ud); |
| break; |
| |
| case LUA_TSTRING: |
| smp->data.type = SMP_T_STR; |
| smp->flags |= SMP_F_CONST; |
| smp->data.u.str.area = (char *)lua_tolstring(L, ud, &smp->data.u.str.data); |
| /* We don't know the actual size of the underlying allocation, so be conservative. */ |
| smp->data.u.str.size = smp->data.u.str.data+1; /* count the terminating null byte */ |
| smp->data.u.str.head = 0; |
| break; |
| |
| case LUA_TUSERDATA: |
| case LUA_TNIL: |
| case LUA_TTABLE: |
| case LUA_TFUNCTION: |
| case LUA_TTHREAD: |
| case LUA_TLIGHTUSERDATA: |
| case LUA_TNONE: |
| default: |
| smp->data.type = SMP_T_BOOL; |
| smp->data.u.sint = 0; |
| break; |
| } |
| return 1; |
| } |
| |
| /* This function check the "argp" built by another conversion function |
| * is in accord with the expected argp defined by the "mask". The function |
| * returns true or false. It can be adjust the types if there compatibles. |
| * |
| * This function assumes that the argp argument contains ARGM_NBARGS + 1 |
| * entries and that there is at least one stop at the last position. |
| */ |
| __LJMP int hlua_lua2arg_check(lua_State *L, int first, struct arg *argp, |
| uint64_t mask, struct proxy *p) |
| { |
| int min_arg; |
| int idx; |
| struct proxy *px; |
| struct userlist *ul; |
| struct my_regex *reg; |
| const char *msg = NULL; |
| char *sname, *pname, *err = NULL; |
| |
| idx = 0; |
| min_arg = ARGM(mask); |
| mask >>= ARGM_BITS; |
| |
| while (1) { |
| struct buffer tmp = BUF_NULL; |
| |
| /* Check for mandatory arguments. */ |
| if (argp[idx].type == ARGT_STOP) { |
| if (idx < min_arg) { |
| |
| /* If miss other argument than the first one, we return an error. */ |
| if (idx > 0) { |
| msg = "Mandatory argument expected"; |
| goto error; |
| } |
| |
| /* If first argument have a certain type, some default values |
| * may be used. See the function smp_resolve_args(). |
| */ |
| switch (mask & ARGT_MASK) { |
| |
| case ARGT_FE: |
| if (!(p->cap & PR_CAP_FE)) { |
| msg = "Mandatory argument expected"; |
| goto error; |
| } |
| argp[idx].data.prx = p; |
| argp[idx].type = ARGT_FE; |
| argp[idx+1].type = ARGT_STOP; |
| break; |
| |
| case ARGT_BE: |
| if (!(p->cap & PR_CAP_BE)) { |
| msg = "Mandatory argument expected"; |
| goto error; |
| } |
| argp[idx].data.prx = p; |
| argp[idx].type = ARGT_BE; |
| argp[idx+1].type = ARGT_STOP; |
| break; |
| |
| case ARGT_TAB: |
| if (!p->table) { |
| msg = "Mandatory argument expected"; |
| goto error; |
| } |
| argp[idx].data.t = p->table; |
| argp[idx].type = ARGT_TAB; |
| argp[idx+1].type = ARGT_STOP; |
| break; |
| |
| default: |
| msg = "Mandatory argument expected"; |
| goto error; |
| break; |
| } |
| } |
| break; |
| } |
| |
| /* Check for exceed the number of required argument. */ |
| if ((mask & ARGT_MASK) == ARGT_STOP && |
| argp[idx].type != ARGT_STOP) { |
| msg = "Last argument expected"; |
| goto error; |
| } |
| |
| if ((mask & ARGT_MASK) == ARGT_STOP && |
| argp[idx].type == ARGT_STOP) { |
| break; |
| } |
| |
| /* Convert some argument types. All string in argp[] are for not |
| * duplicated yet. |
| */ |
| switch (mask & ARGT_MASK) { |
| case ARGT_SINT: |
| if (argp[idx].type != ARGT_SINT) { |
| msg = "integer expected"; |
| goto error; |
| } |
| argp[idx].type = ARGT_SINT; |
| break; |
| |
| case ARGT_TIME: |
| if (argp[idx].type != ARGT_SINT) { |
| msg = "integer expected"; |
| goto error; |
| } |
| argp[idx].type = ARGT_TIME; |
| break; |
| |
| case ARGT_SIZE: |
| if (argp[idx].type != ARGT_SINT) { |
| msg = "integer expected"; |
| goto error; |
| } |
| argp[idx].type = ARGT_SIZE; |
| break; |
| |
| case ARGT_FE: |
| if (argp[idx].type != ARGT_STR) { |
| msg = "string expected"; |
| goto error; |
| } |
| argp[idx].data.prx = proxy_fe_by_name(argp[idx].data.str.area); |
| if (!argp[idx].data.prx) { |
| msg = "frontend doesn't exist"; |
| goto error; |
| } |
| argp[idx].type = ARGT_FE; |
| break; |
| |
| case ARGT_BE: |
| if (argp[idx].type != ARGT_STR) { |
| msg = "string expected"; |
| goto error; |
| } |
| argp[idx].data.prx = proxy_be_by_name(argp[idx].data.str.area); |
| if (!argp[idx].data.prx) { |
| msg = "backend doesn't exist"; |
| goto error; |
| } |
| argp[idx].type = ARGT_BE; |
| break; |
| |
| case ARGT_TAB: |
| if (argp[idx].type != ARGT_STR) { |
| msg = "string expected"; |
| goto error; |
| } |
| argp[idx].data.t = stktable_find_by_name(argp[idx].data.str.area); |
| if (!argp[idx].data.t) { |
| msg = "table doesn't exist"; |
| goto error; |
| } |
| argp[idx].type = ARGT_TAB; |
| break; |
| |
| case ARGT_SRV: |
| if (argp[idx].type != ARGT_STR) { |
| msg = "string expected"; |
| goto error; |
| } |
| sname = strrchr(argp[idx].data.str.area, '/'); |
| if (sname) { |
| *sname++ = '\0'; |
| pname = argp[idx].data.str.area; |
| px = proxy_be_by_name(pname); |
| if (!px) { |
| msg = "backend doesn't exist"; |
| goto error; |
| } |
| } |
| else { |
| sname = argp[idx].data.str.area; |
| px = p; |
| } |
| argp[idx].data.srv = findserver(px, sname); |
| if (!argp[idx].data.srv) { |
| msg = "server doesn't exist"; |
| goto error; |
| } |
| argp[idx].type = ARGT_SRV; |
| break; |
| |
| case ARGT_IPV4: |
| if (argp[idx].type != ARGT_STR) { |
| msg = "string expected"; |
| goto error; |
| } |
| if (inet_pton(AF_INET, argp[idx].data.str.area, &argp[idx].data.ipv4)) { |
| msg = "invalid IPv4 address"; |
| goto error; |
| } |
| argp[idx].type = ARGT_IPV4; |
| break; |
| |
| case ARGT_MSK4: |
| if (argp[idx].type == ARGT_SINT) |
| len2mask4(argp[idx].data.sint, &argp[idx].data.ipv4); |
| else if (argp[idx].type == ARGT_STR) { |
| if (!str2mask(argp[idx].data.str.area, &argp[idx].data.ipv4)) { |
| msg = "invalid IPv4 mask"; |
| goto error; |
| } |
| } |
| else { |
| msg = "integer or string expected"; |
| goto error; |
| } |
| argp[idx].type = ARGT_MSK4; |
| break; |
| |
| case ARGT_IPV6: |
| if (argp[idx].type != ARGT_STR) { |
| msg = "string expected"; |
| goto error; |
| } |
| if (inet_pton(AF_INET6, argp[idx].data.str.area, &argp[idx].data.ipv6)) { |
| msg = "invalid IPv6 address"; |
| goto error; |
| } |
| argp[idx].type = ARGT_IPV6; |
| break; |
| |
| case ARGT_MSK6: |
| if (argp[idx].type == ARGT_SINT) |
| len2mask6(argp[idx].data.sint, &argp[idx].data.ipv6); |
| else if (argp[idx].type == ARGT_STR) { |
| if (!str2mask6(argp[idx].data.str.area, &argp[idx].data.ipv6)) { |
| msg = "invalid IPv6 mask"; |
| goto error; |
| } |
| } |
| else { |
| msg = "integer or string expected"; |
| goto error; |
| } |
| argp[idx].type = ARGT_MSK6; |
| break; |
| |
| case ARGT_REG: |
| if (argp[idx].type != ARGT_STR) { |
| msg = "string expected"; |
| goto error; |
| } |
| reg = regex_comp(argp[idx].data.str.area, !(argp[idx].type_flags & ARGF_REG_ICASE), 1, &err); |
| if (!reg) { |
| msg = lua_pushfstring(L, "error compiling regex '%s' : '%s'", |
| argp[idx].data.str.area, err); |
| free(err); |
| goto error; |
| } |
| argp[idx].type = ARGT_REG; |
| argp[idx].data.reg = reg; |
| break; |
| |
| case ARGT_USR: |
| if (argp[idx].type != ARGT_STR) { |
| msg = "string expected"; |
| goto error; |
| } |
| if (p->uri_auth && p->uri_auth->userlist && |
| strcmp(p->uri_auth->userlist->name, argp[idx].data.str.area) == 0) |
| ul = p->uri_auth->userlist; |
| else |
| ul = auth_find_userlist(argp[idx].data.str.area); |
| |
| if (!ul) { |
| msg = lua_pushfstring(L, "unable to find userlist '%s'", argp[idx].data.str.area); |
| goto error; |
| } |
| argp[idx].type = ARGT_USR; |
| argp[idx].data.usr = ul; |
| break; |
| |
| case ARGT_STR: |
| if (!chunk_dup(&tmp, &argp[idx].data.str)) { |
| msg = "unable to duplicate string arg"; |
| goto error; |
| } |
| argp[idx].data.str = tmp; |
| break; |
| |
| case ARGT_MAP: |
| msg = "type not yet supported"; |
| goto error; |
| break; |
| |
| } |
| |
| /* Check for type of argument. */ |
| if ((mask & ARGT_MASK) != argp[idx].type) { |
| msg = lua_pushfstring(L, "'%s' expected, got '%s'", |
| arg_type_names[(mask & ARGT_MASK)], |
| arg_type_names[argp[idx].type & ARGT_MASK]); |
| goto error; |
| } |
| |
| /* Next argument. */ |
| mask >>= ARGT_BITS; |
| idx++; |
| } |
| return 0; |
| |
| error: |
| argp[idx].type = ARGT_STOP; |
| free_args(argp); |
| WILL_LJMP(luaL_argerror(L, first + idx, msg)); |
| return 0; /* Never reached */ |
| } |
| |
| /* |
| * The following functions are used to make correspondence between the the |
| * executed lua pointer and the "struct hlua *" that contain the context. |
| * |
| * - hlua_gethlua : return the hlua context associated with an lua_State. |
| * - hlua_sethlua : create the association between hlua context and lua_state. |
| */ |
| static inline struct hlua *hlua_gethlua(lua_State *L) |
| { |
| struct hlua **hlua = lua_getextraspace(L); |
| return *hlua; |
| } |
| static inline void hlua_sethlua(struct hlua *hlua) |
| { |
| struct hlua **hlua_store = lua_getextraspace(hlua->T); |
| *hlua_store = hlua; |
| } |
| |
| /* Will return a non-NULL string indicating the Lua call trace if the caller |
| * currently is executing from within a Lua function. One line per entry will |
| * be emitted, and each extra line will be prefixed with <pfx>. If a current |
| * Lua function is not detected, NULL is returned. |
| */ |
| const char *hlua_show_current_location(const char *pfx) |
| { |
| lua_State *L; |
| lua_Debug ar; |
| |
| /* global or per-thread stack initializing ? */ |
| if (hlua_state_id != -1 && (L = hlua_states[hlua_state_id]) && lua_getstack(L, 0, &ar)) |
| return hlua_traceback(L, pfx); |
| |
| /* per-thread stack running ? */ |
| if (hlua_states[tid + 1] && (L = hlua_states[tid + 1]) && lua_getstack(L, 0, &ar)) |
| return hlua_traceback(L, pfx); |
| |
| /* global stack running ? */ |
| if (hlua_states[0] && (L = hlua_states[0]) && lua_getstack(L, 0, &ar)) |
| return hlua_traceback(L, pfx); |
| |
| return NULL; |
| } |
| |
| /* This function is used to send logs. It try to send on screen (stderr) |
| * and on the default syslog server. |
| */ |
| static inline void hlua_sendlog(struct proxy *px, int level, const char *msg) |
| { |
| struct tm tm; |
| char *p; |
| |
| /* Cleanup the log message. */ |
| p = trash.area; |
| for (; *msg != '\0'; msg++, p++) { |
| if (p >= trash.area + trash.size - 1) { |
| /* Break the message if exceed the buffer size. */ |
| *(p-4) = ' '; |
| *(p-3) = '.'; |
| *(p-2) = '.'; |
| *(p-1) = '.'; |
| break; |
| } |
| if (isprint((unsigned char)*msg)) |
| *p = *msg; |
| else |
| *p = '.'; |
| } |
| *p = '\0'; |
| |
| send_log(px, level, "%s\n", trash.area); |
| if (!(global.mode & MODE_QUIET) || (global.mode & (MODE_VERBOSE | MODE_STARTING))) { |
| if (level == LOG_DEBUG && !(global.mode & MODE_DEBUG)) |
| return; |
| |
| get_localtime(date.tv_sec, &tm); |
| fprintf(stderr, "[%s] %03d/%02d%02d%02d (%d) : %s\n", |
| log_levels[level], tm.tm_yday, tm.tm_hour, tm.tm_min, tm.tm_sec, |
| (int)getpid(), trash.area); |
| fflush(stderr); |
| } |
| } |
| |
| /* This function just ensure that the yield will be always |
| * returned with a timeout and permit to set some flags |
| * <timeout> is a tick value |
| */ |
| __LJMP void hlua_yieldk(lua_State *L, int nresults, int ctx, |
| lua_KFunction k, int timeout, unsigned int flags) |
| { |
| struct hlua *hlua; |
| |
| /* Get hlua struct, or NULL if we execute from main lua state */ |
| hlua = hlua_gethlua(L); |
| if (!hlua) { |
| return; |
| } |
| |
| /* Set the wake timeout. If timeout is required, we set |
| * the expiration time. |
| */ |
| hlua->wake_time = timeout; |
| |
| hlua->flags |= flags; |
| |
| /* Process the yield. */ |
| MAY_LJMP(lua_yieldk(L, nresults, ctx, k)); |
| } |
| |
| /* This function initialises the Lua environment stored in the stream. |
| * It must be called at the start of the stream. This function creates |
| * an LUA coroutine. It can not be use to crete the main LUA context. |
| * |
| * This function is particular. it initialises a new Lua thread. If the |
| * initialisation fails (example: out of memory error), the lua function |
| * throws an error (longjmp). |
| * |
| * This function manipulates two Lua stacks: the main and the thread. Only |
| * the main stack can fail. The thread is not manipulated. This function |
| * MUST NOT manipulate the created thread stack state, because it is not |
| * protected against errors thrown by the thread stack. |
| */ |
| int hlua_ctx_init(struct hlua *lua, int state_id, struct task *task) |
| { |
| lua->Mref = LUA_REFNIL; |
| lua->flags = 0; |
| lua->gc_count = 0; |
| lua->wake_time = TICK_ETERNITY; |
| lua->state_id = state_id; |
| hlua_timer_init(&lua->timer, 0); /* default value, no timeout */ |
| LIST_INIT(&lua->com); |
| MT_LIST_INIT(&lua->hc_list); |
| if (!SET_SAFE_LJMP_PARENT(lua)) { |
| lua->Tref = LUA_REFNIL; |
| return 0; |
| } |
| lua->T = lua_newthread(hlua_states[state_id]); |
| if (!lua->T) { |
| lua->Tref = LUA_REFNIL; |
| RESET_SAFE_LJMP_PARENT(lua); |
| return 0; |
| } |
| hlua_sethlua(lua); |
| lua->Tref = luaL_ref(hlua_states[state_id], LUA_REGISTRYINDEX); |
| lua->task = task; |
| RESET_SAFE_LJMP_PARENT(lua); |
| return 1; |
| } |
| |
| /* kill all associated httpclient to this hlua task |
| * We must take extra precautions as we're manipulating lua-exposed |
| * objects without the main lua lock. |
| */ |
| static void hlua_httpclient_destroy_all(struct hlua *hlua) |
| { |
| struct hlua_httpclient *hlua_hc; |
| |
| /* use thread-safe accessors for hc_list since GC cycle initiated by |
| * another thread sharing the same main lua stack (lua coroutine) |
| * could execute hlua_httpclient_gc() on the hlua->hc_list items |
| * in parallel: Lua GC applies on the main stack, it is not limited to |
| * a single coroutine stack, see Github issue #2037 for reference. |
| * Remember, coroutines created using lua_newthread() are not meant to |
| * be thread safe in Lua. (From lua co-author: |
| * http://lua-users.org/lists/lua-l/2011-07/msg00072.html) |
| * |
| * This security measure is superfluous when 'lua-load-per-thread' is used |
| * since in this case coroutines exclusively run on the same thread |
| * (main stack is not shared between OS threads). |
| */ |
| while ((hlua_hc = MT_LIST_POP(&hlua->hc_list, typeof(hlua_hc), by_hlua))) { |
| httpclient_stop_and_destroy(hlua_hc->hc); |
| hlua_hc->hc = NULL; |
| } |
| } |
| |
| |
| /* Used to destroy the Lua coroutine when the attached stream or task |
| * is destroyed. The destroy also the memory context. The struct "lua" |
| * will be freed. |
| */ |
| void hlua_ctx_destroy(struct hlua *lua) |
| { |
| if (!lua) |
| return; |
| |
| if (!lua->T) |
| goto end; |
| |
| /* clean all running httpclient */ |
| hlua_httpclient_destroy_all(lua); |
| |
| /* Purge all the pending signals. */ |
| notification_purge(&lua->com); |
| |
| if (!SET_SAFE_LJMP(lua)) |
| return; |
| luaL_unref(lua->T, LUA_REGISTRYINDEX, lua->Mref); |
| RESET_SAFE_LJMP(lua); |
| |
| if (!SET_SAFE_LJMP_PARENT(lua)) |
| return; |
| luaL_unref(hlua_states[lua->state_id], LUA_REGISTRYINDEX, lua->Tref); |
| RESET_SAFE_LJMP_PARENT(lua); |
| /* Forces a garbage collecting process. If the Lua program is finished |
| * without error, we run the GC on the thread pointer. Its freed all |
| * the unused memory. |
| * If the thread is finnish with an error or is currently yielded, |
| * it seems that the GC applied on the thread doesn't clean anything, |
| * so e run the GC on the main thread. |
| * NOTE: maybe this action locks all the Lua threads untiml the en of |
| * the garbage collection. |
| */ |
| if (lua->gc_count) { |
| if (!SET_SAFE_LJMP_PARENT(lua)) |
| return; |
| lua_gc(hlua_states[lua->state_id], LUA_GCCOLLECT, 0); |
| RESET_SAFE_LJMP_PARENT(lua); |
| } |
| |
| lua->T = NULL; |
| |
| end: |
| pool_free(pool_head_hlua, lua); |
| } |
| |
| /* This function is used to restore the Lua context when a coroutine |
| * fails. This function copy the common memory between old coroutine |
| * and the new coroutine. The old coroutine is destroyed, and its |
| * replaced by the new coroutine. |
| * If the flag "keep_msg" is set, the last entry of the old is assumed |
| * as string error message and it is copied in the new stack. |
| */ |
| static int hlua_ctx_renew(struct hlua *lua, int keep_msg) |
| { |
| lua_State *T; |
| int new_ref; |
| |
| /* New Lua coroutine. */ |
| T = lua_newthread(hlua_states[lua->state_id]); |
| if (!T) |
| return 0; |
| |
| /* Copy last error message. */ |
| if (keep_msg) |
| lua_xmove(lua->T, T, 1); |
| |
| /* Copy data between the coroutines. */ |
| lua_rawgeti(lua->T, LUA_REGISTRYINDEX, lua->Mref); |
| lua_xmove(lua->T, T, 1); |
| new_ref = luaL_ref(T, LUA_REGISTRYINDEX); /* Value popped. */ |
| |
| /* Destroy old data. */ |
| luaL_unref(lua->T, LUA_REGISTRYINDEX, lua->Mref); |
| |
| /* The thread is garbage collected by Lua. */ |
| luaL_unref(hlua_states[lua->state_id], LUA_REGISTRYINDEX, lua->Tref); |
| |
| /* Fill the struct with the new coroutine values. */ |
| lua->Mref = new_ref; |
| lua->T = T; |
| lua->Tref = luaL_ref(hlua_states[lua->state_id], LUA_REGISTRYINDEX); |
| |
| /* Set context. */ |
| hlua_sethlua(lua); |
| |
| return 1; |
| } |
| |
| void hlua_hook(lua_State *L, lua_Debug *ar) |
| { |
| struct hlua *hlua; |
| |
| /* Get hlua struct, or NULL if we execute from main lua state */ |
| hlua = hlua_gethlua(L); |
| if (!hlua) |
| return; |
| |
| if (hlua->T != L) { |
| /* We don't want to enforce a yield on a sub coroutine, since |
| * we have no guarantees that the yield will be handled properly. |
| * Indeed, only the hlua->T coroutine is being handled through |
| * hlua_ctx_resume() function. |
| * |
| * Instead, we simply check for timeouts and wait for the sub |
| * coroutine to finish.. |
| */ |
| goto check_timeout; |
| } |
| |
| /* Lua cannot yield when its returning from a function, |
| * so, we can fix the interrupt hook to 1 instruction, |
| * expecting that the function is finished. |
| */ |
| if (lua_gethookmask(L) & LUA_MASKRET) { |
| lua_sethook(hlua->T, hlua_hook, LUA_MASKCOUNT, 1); |
| return; |
| } |
| |
| /* If we interrupt the Lua processing in yieldable state, we yield. |
| * If the state is not yieldable, trying yield causes an error. |
| */ |
| if (lua_isyieldable(L)) { |
| /* note: for converters/fetches.. where yielding is not allowed |
| * hlua_ctx_resume() will simply perform a goto resume_execution |
| * instead of rescheduling hlua->task. |
| * also: hlua_ctx_resume() will take care of checking execution |
| * timeout and re-applying the hook as needed. |
| */ |
| MAY_LJMP(hlua_yieldk(L, 0, 0, NULL, TICK_ETERNITY, HLUA_CTRLYIELD)); |
| /* lua docs says that the hook should return immediately after lua_yieldk |
| * |
| * From: https://www.lua.org/manual/5.3/manual.html#lua_yieldk |
| * |
| * Moreover, it seems that we don't want to continue after the yield |
| * because the end of the function is about handling unyieldable function, |
| * which is not the case here. |
| * |
| * ->if we don't return lua_sethook gets incorrectly set with MASKRET later |
| * in the function. |
| */ |
| return; |
| } |
| |
| check_timeout: |
| /* If we cannot yield, check the timeout. */ |
| if (!hlua_timer_check(&hlua->timer)) { |
| lua_pushfstring(L, "execution timeout"); |
| WILL_LJMP(lua_error(L)); |
| } |
| |
| /* Try to interrupt the process at the end of the current |
| * unyieldable function. |
| */ |
| lua_sethook(hlua->T, hlua_hook, LUA_MASKRET|LUA_MASKCOUNT, hlua_nb_instruction); |
| } |
| |
| /* This function start or resumes the Lua stack execution. If the flag |
| * "yield_allowed" if no set and the LUA stack execution returns a yield |
| * The function return an error. |
| * |
| * The function can returns 4 values: |
| * - HLUA_E_OK : The execution is terminated without any errors. |
| * - HLUA_E_AGAIN : The execution must continue at the next associated |
| * task wakeup. |
| * - HLUA_E_ERRMSG : An error has occurred, an error message is set in |
| * the top of the stack. |
| * - HLUA_E_ERR : An error has occurred without error message. |
| * |
| * If an error occurred, the stack is renewed and it is ready to run new |
| * LUA code. |
| */ |
| static enum hlua_exec hlua_ctx_resume(struct hlua *lua, int yield_allowed) |
| { |
| #if defined(LUA_VERSION_NUM) && LUA_VERSION_NUM >= 504 |
| int nres; |
| #endif |
| int ret; |
| const char *msg; |
| const char *trace; |
| |
| /* Lock the whole Lua execution. This lock must be before the |
| * label "resume_execution". |
| */ |
| hlua_lock(lua); |
| |
| /* reset the timer as we might be re-entering the function to |
| * resume the coroutine after a successful yield |
| * (cumulative time will be updated) |
| */ |
| hlua_timer_reset(&lua->timer); |
| |
| resume_execution: |
| |
| /* This hook interrupts the Lua processing each 'hlua_nb_instruction' |
| * instructions. it is used for preventing infinite loops. |
| */ |
| lua_sethook(lua->T, hlua_hook, LUA_MASKCOUNT, hlua_nb_instruction); |
| |
| /* Remove all flags except the running flags. */ |
| HLUA_SET_RUN(lua); |
| HLUA_CLR_CTRLYIELD(lua); |
| HLUA_CLR_WAKERESWR(lua); |
| HLUA_CLR_WAKEREQWR(lua); |
| HLUA_CLR_NOYIELD(lua); |
| if (!yield_allowed) |
| HLUA_SET_NOYIELD(lua); |
| |
| /* reset wake_time. */ |
| lua->wake_time = TICK_ETERNITY; |
| |
| /* start the timer as we're about to start lua processing */ |
| hlua_timer_start(&lua->timer); |
| |
| /* Call the function. */ |
| #if defined(LUA_VERSION_NUM) && LUA_VERSION_NUM >= 504 |
| ret = lua_resume(lua->T, hlua_states[lua->state_id], lua->nargs, &nres); |
| #else |
| ret = lua_resume(lua->T, hlua_states[lua->state_id], lua->nargs); |
| #endif |
| |
| /* out of lua processing, stop the timer */ |
| hlua_timer_stop(&lua->timer); |
| |
| switch (ret) { |
| |
| case LUA_OK: |
| ret = HLUA_E_OK; |
| break; |
| |
| case LUA_YIELD: |
| /* Check if the execution timeout is expired. If it is the case, we |
| * break the Lua execution. |
| */ |
| if (!hlua_timer_check(&lua->timer)) { |
| lua_settop(lua->T, 0); /* Empty the stack. */ |
| ret = HLUA_E_ETMOUT; |
| break; |
| } |
| /* Process the forced yield. if the general yield is not allowed or |
| * if no task were associated this the current Lua execution |
| * coroutine, we resume the execution. Else we want to return in the |
| * scheduler and we want to be waked up again, to continue the |
| * current Lua execution. So we schedule our own task. |
| */ |
| if (HLUA_IS_CTRLYIELDING(lua)) { |
| if (!yield_allowed || !lua->task) |
| goto resume_execution; |
| task_wakeup(lua->task, TASK_WOKEN_MSG); |
| } |
| if (!yield_allowed) { |
| lua_settop(lua->T, 0); /* Empty the stack. */ |
| ret = HLUA_E_YIELD; |
| break; |
| } |
| ret = HLUA_E_AGAIN; |
| break; |
| |
| case LUA_ERRRUN: |
| |
| /* Special exit case. The traditional exit is returned as an error |
| * because the errors ares the only one mean to return immediately |
| * from and lua execution. |
| */ |
| if (lua->flags & HLUA_EXIT) { |
| ret = HLUA_E_OK; |
| hlua_ctx_renew(lua, 1); |
| break; |
| } |
| |
| lua->wake_time = TICK_ETERNITY; |
| if (!lua_checkstack(lua->T, 1)) { |
| ret = HLUA_E_ERR; |
| break; |
| } |
| msg = lua_tostring(lua->T, -1); |
| lua_settop(lua->T, 0); /* Empty the stack. */ |
| lua_pop(lua->T, 1); |
| trace = hlua_traceback(lua->T, ", "); |
| if (msg) |
| lua_pushfstring(lua->T, "[state-id %d] runtime error: %s from %s", lua->state_id, msg, trace); |
| else |
| lua_pushfstring(lua->T, "[state-id %d] unknown runtime error from %s", lua->state_id, trace); |
| ret = HLUA_E_ERRMSG; |
| break; |
| |
| case LUA_ERRMEM: |
| lua->wake_time = TICK_ETERNITY; |
| lua_settop(lua->T, 0); /* Empty the stack. */ |
| ret = HLUA_E_NOMEM; |
| break; |
| |
| case LUA_ERRERR: |
| lua->wake_time = TICK_ETERNITY; |
| if (!lua_checkstack(lua->T, 1)) { |
| ret = HLUA_E_ERR; |
| break; |
| } |
| msg = lua_tostring(lua->T, -1); |
| lua_settop(lua->T, 0); /* Empty the stack. */ |
| lua_pop(lua->T, 1); |
| if (msg) |
| lua_pushfstring(lua->T, "[state-id %d] message handler error: %s", lua->state_id, msg); |
| else |
| lua_pushfstring(lua->T, "[state-id %d] message handler error", lua->state_id); |
| ret = HLUA_E_ERRMSG; |
| break; |
| |
| default: |
| lua->wake_time = TICK_ETERNITY; |
| lua_settop(lua->T, 0); /* Empty the stack. */ |
| ret = HLUA_E_ERR; |
| break; |
| } |
| |
| switch (ret) { |
| case HLUA_E_AGAIN: |
| break; |
| |
| case HLUA_E_ERRMSG: |
| notification_purge(&lua->com); |
| hlua_ctx_renew(lua, 1); |
| HLUA_CLR_RUN(lua); |
| break; |
| |
| case HLUA_E_ETMOUT: |
| case HLUA_E_NOMEM: |
| case HLUA_E_YIELD: |
| case HLUA_E_ERR: |
| HLUA_CLR_RUN(lua); |
| notification_purge(&lua->com); |
| hlua_ctx_renew(lua, 0); |
| break; |
| |
| case HLUA_E_OK: |
| HLUA_CLR_RUN(lua); |
| notification_purge(&lua->com); |
| break; |
| } |
| |
| /* This is the main exit point, remove the Lua lock. */ |
| hlua_unlock(lua); |
| |
| return ret; |
| } |
| |
| /* This function exit the current code. */ |
| __LJMP static int hlua_done(lua_State *L) |
| { |
| struct hlua *hlua; |
| |
| /* Get hlua struct, or NULL if we execute from main lua state */ |
| hlua = hlua_gethlua(L); |
| if (!hlua) |
| return 0; |
| |
| hlua->flags |= HLUA_EXIT; |
| WILL_LJMP(lua_error(L)); |
| |
| return 0; |
| } |
| |
| /* This function is an LUA binding. It provides a function |
| * for deleting ACL from a referenced ACL file. |
| */ |
| __LJMP static int hlua_del_acl(lua_State *L) |
| { |
| const char *name; |
| const char *key; |
| struct pat_ref *ref; |
| |
| MAY_LJMP(check_args(L, 2, "del_acl")); |
| |
| name = MAY_LJMP(luaL_checkstring(L, 1)); |
| key = MAY_LJMP(luaL_checkstring(L, 2)); |
| |
| ref = pat_ref_lookup(name); |
| if (!ref) |
| WILL_LJMP(luaL_error(L, "'del_acl': unknown acl file '%s'", name)); |
| |
| HA_SPIN_LOCK(PATREF_LOCK, &ref->lock); |
| pat_ref_delete(ref, key); |
| HA_SPIN_UNLOCK(PATREF_LOCK, &ref->lock); |
| return 0; |
| } |
| |
| /* This function is an LUA binding. It provides a function |
| * for deleting map entry from a referenced map file. |
| */ |
| static int hlua_del_map(lua_State *L) |
| { |
| const char *name; |
| const char *key; |
| struct pat_ref *ref; |
| |
| MAY_LJMP(check_args(L, 2, "del_map")); |
| |
| name = MAY_LJMP(luaL_checkstring(L, 1)); |
| key = MAY_LJMP(luaL_checkstring(L, 2)); |
| |
| ref = pat_ref_lookup(name); |
| if (!ref) |
| WILL_LJMP(luaL_error(L, "'del_map': unknown acl file '%s'", name)); |
| |
| HA_SPIN_LOCK(PATREF_LOCK, &ref->lock); |
| pat_ref_delete(ref, key); |
| HA_SPIN_UNLOCK(PATREF_LOCK, &ref->lock); |
| return 0; |
| } |
| |
| /* This function is an LUA binding. It provides a function |
| * for adding ACL pattern from a referenced ACL file. |
| */ |
| static int hlua_add_acl(lua_State *L) |
| { |
| const char *name; |
| const char *key; |
| struct pat_ref *ref; |
| |
| MAY_LJMP(check_args(L, 2, "add_acl")); |
| |
| name = MAY_LJMP(luaL_checkstring(L, 1)); |
| key = MAY_LJMP(luaL_checkstring(L, 2)); |
| |
| ref = pat_ref_lookup(name); |
| if (!ref) |
| WILL_LJMP(luaL_error(L, "'add_acl': unknown acl file '%s'", name)); |
| |
| HA_SPIN_LOCK(PATREF_LOCK, &ref->lock); |
| if (pat_ref_find_elt(ref, key) == NULL) |
| pat_ref_add(ref, key, NULL, NULL); |
| HA_SPIN_UNLOCK(PATREF_LOCK, &ref->lock); |
| return 0; |
| } |
| |
| /* This function is an LUA binding. It provides a function |
| * for setting map pattern and sample from a referenced map |
| * file. |
| */ |
| static int hlua_set_map(lua_State *L) |
| { |
| const char *name; |
| const char *key; |
| const char *value; |
| struct pat_ref *ref; |
| |
| MAY_LJMP(check_args(L, 3, "set_map")); |
| |
| name = MAY_LJMP(luaL_checkstring(L, 1)); |
| key = MAY_LJMP(luaL_checkstring(L, 2)); |
| value = MAY_LJMP(luaL_checkstring(L, 3)); |
| |
| ref = pat_ref_lookup(name); |
| if (!ref) |
| WILL_LJMP(luaL_error(L, "'set_map': unknown map file '%s'", name)); |
| |
| HA_SPIN_LOCK(PATREF_LOCK, &ref->lock); |
| if (pat_ref_find_elt(ref, key) != NULL) |
| pat_ref_set(ref, key, value, NULL); |
| else |
| pat_ref_add(ref, key, value, NULL); |
| HA_SPIN_UNLOCK(PATREF_LOCK, &ref->lock); |
| return 0; |
| } |
| |
| /* A class is a lot of memory that contain data. This data can be a table, |
| * an integer or user data. This data is associated with a metatable. This |
| * metatable have an original version registered in the global context with |
| * the name of the object (_G[<name>] = <metable> ). |
| * |
| * A metable is a table that modify the standard behavior of a standard |
| * access to the associated data. The entries of this new metatable are |
| * defined as is: |
| * |
| * http://lua-users.org/wiki/MetatableEvents |
| * |
| * __index |
| * |
| * we access an absent field in a table, the result is nil. This is |
| * true, but it is not the whole truth. Actually, such access triggers |
| * the interpreter to look for an __index metamethod: If there is no |
| * such method, as usually happens, then the access results in nil; |
| * otherwise, the metamethod will provide the result. |
| * |
| * Control 'prototype' inheritance. When accessing "myTable[key]" and |
| * the key does not appear in the table, but the metatable has an __index |
| * property: |
| * |
| * - if the value is a function, the function is called, passing in the |
| * table and the key; the return value of that function is returned as |
| * the result. |
| * |
| * - if the value is another table, the value of the key in that table is |
| * asked for and returned (and if it doesn't exist in that table, but that |
| * table's metatable has an __index property, then it continues on up) |
| * |
| * - Use "rawget(myTable,key)" to skip this metamethod. |
| * |
| * http://www.lua.org/pil/13.4.1.html |
| * |
| * __newindex |
| * |
| * Like __index, but control property assignment. |
| * |
| * __mode - Control weak references. A string value with one or both |
| * of the characters 'k' and 'v' which specifies that the the |
| * keys and/or values in the table are weak references. |
| * |
| * __call - Treat a table like a function. When a table is followed by |
| * parenthesis such as "myTable( 'foo' )" and the metatable has |
| * a __call key pointing to a function, that function is invoked |
| * (passing any specified arguments) and the return value is |
| * returned. |
| * |
| * __metatable - Hide the metatable. When "getmetatable( myTable )" is |
| * called, if the metatable for myTable has a __metatable |
| * key, the value of that key is returned instead of the |
| * actual metatable. |
| * |
| * __tostring - Control string representation. When the builtin |
| * "tostring( myTable )" function is called, if the metatable |
| * for myTable has a __tostring property set to a function, |
| * that function is invoked (passing myTable to it) and the |
| * return value is used as the string representation. |
| * |
| * __len - Control table length. When the table length is requested using |
| * the length operator ( '#' ), if the metatable for myTable has |
| * a __len key pointing to a function, that function is invoked |
| * (passing myTable to it) and the return value used as the value |
| * of "#myTable". |
| * |
| * __gc - Userdata finalizer code. When userdata is set to be garbage |
| * collected, if the metatable has a __gc field pointing to a |
| * function, that function is first invoked, passing the userdata |
| * to it. The __gc metamethod is not called for tables. |
| * (See http://lua-users.org/lists/lua-l/2006-11/msg00508.html) |
| * |
| * Special metamethods for redefining standard operators: |
| * http://www.lua.org/pil/13.1.html |
| * |
| * __add "+" |
| * __sub "-" |
| * __mul "*" |
| * __div "/" |
| * __unm "!" |
| * __pow "^" |
| * __concat ".." |
| * |
| * Special methods for redefining standard relations |
| * http://www.lua.org/pil/13.2.html |
| * |
| * __eq "==" |
| * __lt "<" |
| * __le "<=" |
| */ |
| |
| /* |
| * |
| * |
| * Class Map |
| * |
| * |
| */ |
| |
| /* Returns a struct hlua_map if the stack entry "ud" is |
| * a class session, otherwise it throws an error. |
| */ |
| __LJMP static struct map_descriptor *hlua_checkmap(lua_State *L, int ud) |
| { |
| return MAY_LJMP(hlua_checkudata(L, ud, class_map_ref)); |
| } |
| |
| /* This function is the map constructor. It don't need |
| * the class Map object. It creates and return a new Map |
| * object. It must be called only during "body" or "init" |
| * context because it process some filesystem accesses. |
| */ |
| __LJMP static int hlua_map_new(struct lua_State *L) |
| { |
| const char *fn; |
| int match = PAT_MATCH_STR; |
| struct sample_conv conv; |
| const char *file = ""; |
| int line = 0; |
| lua_Debug ar; |
| char *err = NULL; |
| struct arg args[2]; |
| |
| if (lua_gettop(L) < 1 || lua_gettop(L) > 2) |
| WILL_LJMP(luaL_error(L, "'new' needs at least 1 argument.")); |
| |
| fn = MAY_LJMP(luaL_checkstring(L, 1)); |
| |
| if (lua_gettop(L) >= 2) { |
| match = MAY_LJMP(luaL_checkinteger(L, 2)); |
| if (match < 0 || match >= PAT_MATCH_NUM) |
| WILL_LJMP(luaL_error(L, "'new' needs a valid match method.")); |
| } |
| |
| /* Get Lua filename and line number. */ |
| if (lua_getstack(L, 1, &ar)) { /* check function at level */ |
| lua_getinfo(L, "Sl", &ar); /* get info about it */ |
| if (ar.currentline > 0) { /* is there info? */ |
| file = ar.short_src; |
| line = ar.currentline; |
| } |
| } |
| |
| /* fill fake sample_conv struct. */ |
| conv.kw = ""; /* unused. */ |
| conv.process = NULL; /* unused. */ |
| conv.arg_mask = 0; /* unused. */ |
| conv.val_args = NULL; /* unused. */ |
| conv.out_type = SMP_T_STR; |
| conv.private = (void *)(long)match; |
| switch (match) { |
| case PAT_MATCH_STR: conv.in_type = SMP_T_STR; break; |
| case PAT_MATCH_BEG: conv.in_type = SMP_T_STR; break; |
| case PAT_MATCH_SUB: conv.in_type = SMP_T_STR; break; |
| case PAT_MATCH_DIR: conv.in_type = SMP_T_STR; break; |
| case PAT_MATCH_DOM: conv.in_type = SMP_T_STR; break; |
| case PAT_MATCH_END: conv.in_type = SMP_T_STR; break; |
| case PAT_MATCH_REG: conv.in_type = SMP_T_STR; break; |
| case PAT_MATCH_INT: conv.in_type = SMP_T_SINT; break; |
| case PAT_MATCH_IP: conv.in_type = SMP_T_ADDR; break; |
| default: |
| WILL_LJMP(luaL_error(L, "'new' doesn't support this match mode.")); |
| } |
| |
| /* fill fake args. */ |
| args[0].type = ARGT_STR; |
| args[0].data.str.area = strdup(fn); |
| args[0].data.str.data = strlen(fn); |
| args[0].data.str.size = args[0].data.str.data+1; |
| args[1].type = ARGT_STOP; |
| |
| /* load the map. */ |
| if (!sample_load_map(args, &conv, file, line, &err)) { |
| /* error case: we can't use luaL_error because we must |
| * free the err variable. |
| */ |
| luaL_where(L, 1); |
| lua_pushfstring(L, "'new': %s.", err); |
| lua_concat(L, 2); |
| free(err); |
| chunk_destroy(&args[0].data.str); |
| WILL_LJMP(lua_error(L)); |
| } |
| |
| /* create the lua object. */ |
| lua_newtable(L); |
| lua_pushlightuserdata(L, args[0].data.map); |
| lua_rawseti(L, -2, 0); |
| |
| /* Pop a class Map metatable and affect it to the userdata. */ |
| lua_rawgeti(L, LUA_REGISTRYINDEX, class_map_ref); |
| lua_setmetatable(L, -2); |
| |
| |
| return 1; |
| } |
| |
| __LJMP static inline int _hlua_map_lookup(struct lua_State *L, int str) |
| { |
| struct map_descriptor *desc; |
| struct pattern *pat; |
| struct sample smp; |
| |
| MAY_LJMP(check_args(L, 2, "lookup")); |
| desc = MAY_LJMP(hlua_checkmap(L, 1)); |
| if (desc->pat.expect_type == SMP_T_SINT) { |
| smp.data.type = SMP_T_SINT; |
| smp.data.u.sint = MAY_LJMP(luaL_checkinteger(L, 2)); |
| } |
| else { |
| smp.data.type = SMP_T_STR; |
| smp.flags = SMP_F_CONST; |
| smp.data.u.str.area = (char *)MAY_LJMP(luaL_checklstring(L, 2, (size_t *)&smp.data.u.str.data)); |
| smp.data.u.str.size = smp.data.u.str.data + 1; |
| } |
| |
| pat = pattern_exec_match(&desc->pat, &smp, 1); |
| if (!pat || !pat->data) { |
| if (str) |
| lua_pushstring(L, ""); |
| else |
| lua_pushnil(L); |
| return 1; |
| } |
| |
| /* The Lua pattern must return a string, so we can't check the returned type */ |
| lua_pushlstring(L, pat->data->u.str.area, pat->data->u.str.data); |
| return 1; |
| } |
| |
| __LJMP static int hlua_map_lookup(struct lua_State *L) |
| { |
| return _hlua_map_lookup(L, 0); |
| } |
| |
| __LJMP static int hlua_map_slookup(struct lua_State *L) |
| { |
| return _hlua_map_lookup(L, 1); |
| } |
| |
| /* |
| * |
| * |
| * Class Socket |
| * |
| * |
| */ |
| |
| __LJMP static struct hlua_socket *hlua_checksocket(lua_State *L, int ud) |
| { |
| return MAY_LJMP(hlua_checkudata(L, ud, class_socket_ref)); |
| } |
| |
| /* This function is the handler called for each I/O on the established |
| * connection. It is used for notify space available to send or data |
| * received. |
| */ |
| static void hlua_socket_handler(struct appctx *appctx) |
| { |
| struct hlua_csk_ctx *ctx = appctx->svcctx; |
| struct stconn *sc = appctx_sc(appctx); |
| |
| if (unlikely(se_fl_test(appctx->sedesc, (SE_FL_EOS|SE_FL_ERROR|SE_FL_SHR|SE_FL_SHW)))) { |
| notification_wake(&ctx->wake_on_read); |
| notification_wake(&ctx->wake_on_write); |
| return; |
| } |
| |
| if (ctx->die) { |
| se_fl_set(appctx->sedesc, SE_FL_EOI|SE_FL_EOS); |
| notification_wake(&ctx->wake_on_read); |
| notification_wake(&ctx->wake_on_write); |
| return; |
| } |
| |
| /* If we can't write, wakeup the pending write signals. */ |
| if (channel_output_closed(sc_ic(sc))) |
| notification_wake(&ctx->wake_on_write); |
| |
| /* If we can't read, wakeup the pending read signals. */ |
| if (channel_input_closed(sc_oc(sc))) |
| notification_wake(&ctx->wake_on_read); |
| |
| /* if the connection is not established, inform the stream that we want |
| * to be notified whenever the connection completes. |
| */ |
| if (sc_opposite(sc)->state < SC_ST_EST) { |
| applet_need_more_data(appctx); |
| se_need_remote_conn(appctx->sedesc); |
| applet_have_more_data(appctx); |
| return; |
| } |
| |
| /* This function is called after the connect. */ |
| ctx->connected = 1; |
| |
| /* Wake the tasks which wants to write if the buffer have available space. */ |
| if (channel_may_recv(sc_ic(sc))) |
| notification_wake(&ctx->wake_on_write); |
| |
| /* Wake the tasks which wants to read if the buffer contains data. */ |
| if (!channel_is_empty(sc_oc(sc))) |
| notification_wake(&ctx->wake_on_read); |
| |
| /* If write notifications are registered, we considers we want |
| * to write, so we clear the blocking flag. |
| */ |
| if (notification_registered(&ctx->wake_on_write)) |
| applet_have_more_data(appctx); |
| } |
| |
| static int hlua_socket_init(struct appctx *appctx) |
| { |
| struct hlua_csk_ctx *ctx = appctx->svcctx; |
| struct stream *s; |
| |
| if (appctx_finalize_startup(appctx, socket_proxy, &BUF_NULL) == -1) |
| goto error; |
| |
| s = appctx_strm(appctx); |
| |
| /* Configure "right" stream connector. This stconn is used to connect |
| * and retrieve data from the server. The connection is initialized |
| * with the "struct server". |
| */ |
| sc_set_state(s->scb, SC_ST_ASS); |
| |
| /* Force destination server. */ |
| s->flags |= SF_DIRECT | SF_ASSIGNED | SF_BE_ASSIGNED; |
| s->target = &socket_tcp->obj_type; |
| |
| ctx->appctx = appctx; |
| return 0; |
| |
| error: |
| return -1; |
| } |
| |
| /* This function is called when the "struct stream" is destroyed. |
| * Remove the link from the object to this stream. |
| * Wake all the pending signals. |
| */ |
| static void hlua_socket_release(struct appctx *appctx) |
| { |
| struct hlua_csk_ctx *ctx = appctx->svcctx; |
| struct xref *peer; |
| |
| /* Remove my link in the original objects. */ |
| peer = xref_get_peer_and_lock(&ctx->xref); |
| if (peer) |
| xref_disconnect(&ctx->xref, peer); |
| |
| /* Wake all the task waiting for me. */ |
| notification_wake(&ctx->wake_on_read); |
| notification_wake(&ctx->wake_on_write); |
| } |
| |
| /* If the garbage collectio of the object is launch, nobody |
| * uses this object. If the stream does not exists, just quit. |
| * Send the shutdown signal to the stream. In some cases, |
| * pending signal can rest in the read and write lists. destroy |
| * it. |
| */ |
| __LJMP static int hlua_socket_gc(lua_State *L) |
| { |
| struct hlua_socket *socket; |
| struct hlua_csk_ctx *ctx; |
| struct xref *peer; |
| |
| MAY_LJMP(check_args(L, 1, "__gc")); |
| |
| socket = MAY_LJMP(hlua_checksocket(L, 1)); |
| peer = xref_get_peer_and_lock(&socket->xref); |
| if (!peer) |
| return 0; |
| |
| ctx = container_of(peer, struct hlua_csk_ctx, xref); |
| |
| /* Set the flag which destroy the session. */ |
| ctx->die = 1; |
| appctx_wakeup(ctx->appctx); |
| |
| /* Remove all reference between the Lua stack and the coroutine stream. */ |
| xref_disconnect(&socket->xref, peer); |
| return 0; |
| } |
| |
| /* The close function send shutdown signal and break the |
| * links between the stream and the object. |
| */ |
| __LJMP static int hlua_socket_close_helper(lua_State *L) |
| { |
| struct hlua_socket *socket; |
| struct hlua_csk_ctx *ctx; |
| struct xref *peer; |
| struct hlua *hlua; |
| |
| /* Get hlua struct, or NULL if we execute from main lua state */ |
| hlua = hlua_gethlua(L); |
| if (!hlua) |
| return 0; |
| |
| socket = MAY_LJMP(hlua_checksocket(L, 1)); |
| |
| /* Check if we run on the same thread than the xreator thread. |
| * We cannot access to the socket if the thread is different. |
| */ |
| if (socket->tid != tid) |
| WILL_LJMP(luaL_error(L, "connect: cannot use socket on other thread")); |
| |
| peer = xref_get_peer_and_lock(&socket->xref); |
| if (!peer) |
| return 0; |
| |
| hlua->gc_count--; |
| ctx = container_of(peer, struct hlua_csk_ctx, xref); |
| |
| /* Set the flag which destroy the session. */ |
| ctx->die = 1; |
| appctx_wakeup(ctx->appctx); |
| |
| /* Remove all reference between the Lua stack and the coroutine stream. */ |
| xref_disconnect(&socket->xref, peer); |
| return 0; |
| } |
| |
| /* The close function calls close_helper. |
| */ |
| __LJMP static int hlua_socket_close(lua_State *L) |
| { |
| MAY_LJMP(check_args(L, 1, "close")); |
| return hlua_socket_close_helper(L); |
| } |
| |
| /* This Lua function assumes that the stack contain three parameters. |
| * 1 - USERDATA containing a struct socket |
| * 2 - INTEGER with values of the macro defined below |
| * If the integer is -1, we must read at most one line. |
| * If the integer is -2, we ust read all the data until the |
| * end of the stream. |
| * If the integer is positive value, we must read a number of |
| * bytes corresponding to this value. |
| */ |
| #define HLSR_READ_LINE (-1) |
| #define HLSR_READ_ALL (-2) |
| __LJMP static int hlua_socket_receive_yield(struct lua_State *L, int status, lua_KContext ctx) |
| { |
| struct hlua_socket *socket = MAY_LJMP(hlua_checksocket(L, 1)); |
| int wanted = lua_tointeger(L, 2); |
| struct hlua *hlua; |
| struct hlua_csk_ctx *csk_ctx; |
| struct appctx *appctx; |
| size_t len; |
| int nblk; |
| const char *blk1; |
| size_t len1; |
| const char *blk2; |
| size_t len2; |
| int skip_at_end = 0; |
| struct channel *oc; |
| struct stream *s; |
| struct xref *peer; |
| int missing_bytes; |
| |
| /* Get hlua struct, or NULL if we execute from main lua state */ |
| hlua = hlua_gethlua(L); |
| |
| /* Check if this lua stack is schedulable. */ |
| if (!hlua || !hlua->task) |
| WILL_LJMP(luaL_error(L, "The 'receive' function is only allowed in " |
| "'frontend', 'backend' or 'task'")); |
| |
| /* Check if we run on the same thread than the xreator thread. |
| * We cannot access to the socket if the thread is different. |
| */ |
| if (socket->tid != tid) |
| WILL_LJMP(luaL_error(L, "connect: cannot use socket on other thread")); |
| |
| /* check for connection break. If some data where read, return it. */ |
| peer = xref_get_peer_and_lock(&socket->xref); |
| if (!peer) |
| goto no_peer; |
| |
| csk_ctx = container_of(peer, struct hlua_csk_ctx, xref); |
| appctx = csk_ctx->appctx; |
| s = appctx_strm(appctx); |
| |
| oc = &s->res; |
| if (wanted == HLSR_READ_LINE) { |
| /* Read line. */ |
| nblk = co_getline_nc(oc, &blk1, &len1, &blk2, &len2); |
| if (nblk < 0) /* Connection close. */ |
| goto connection_closed; |
| if (nblk == 0) /* No data available. */ |
| goto connection_empty; |
| |
| /* remove final \r\n. */ |
| if (nblk == 1) { |
| if (blk1[len1-1] == '\n') { |
| len1--; |
| skip_at_end++; |
| if (blk1[len1-1] == '\r') { |
| len1--; |
| skip_at_end++; |
| } |
| } |
| } |
| else { |
| if (blk2[len2-1] == '\n') { |
| len2--; |
| skip_at_end++; |
| if (blk2[len2-1] == '\r') { |
| len2--; |
| skip_at_end++; |
| } |
| } |
| } |
| } |
| |
| else if (wanted == HLSR_READ_ALL) { |
| /* Read all the available data. */ |
| nblk = co_getblk_nc(oc, &blk1, &len1, &blk2, &len2); |
| if (nblk < 0) /* Connection close. */ |
| goto connection_closed; |
| if (nblk == 0) /* No data available. */ |
| goto connection_empty; |
| } |
| |
| else { |
| /* Read a block of data. */ |
| nblk = co_getblk_nc(oc, &blk1, &len1, &blk2, &len2); |
| if (nblk < 0) /* Connection close. */ |
| goto connection_closed; |
| if (nblk == 0) /* No data available. */ |
| goto connection_empty; |
| |
| missing_bytes = wanted - socket->b.n; |
| if (len1 > missing_bytes) { |
| nblk = 1; |
| len1 = missing_bytes; |
| } if (nblk == 2 && len1 + len2 > missing_bytes) |
| len2 = missing_bytes - len1; |
| } |
| |
| len = len1; |
| |
| luaL_addlstring(&socket->b, blk1, len1); |
| if (nblk == 2) { |
| len += len2; |
| luaL_addlstring(&socket->b, blk2, len2); |
| } |
| |
| /* Consume data. */ |
| co_skip(oc, len + skip_at_end); |
| |
| /* Don't wait anything. */ |
| appctx_wakeup(appctx); |
| |
| /* If the pattern reclaim to read all the data |
| * in the connection, got out. |
| */ |
| if (wanted == HLSR_READ_ALL) |
| goto connection_empty; |
| else if (wanted >= 0 && socket->b.n < wanted) |
| goto connection_empty; |
| |
| /* Return result. */ |
| luaL_pushresult(&socket->b); |
| xref_unlock(&socket->xref, peer); |
| return 1; |
| |
| connection_closed: |
| |
| xref_unlock(&socket->xref, peer); |
| |
| no_peer: |
| |
| /* If the buffer containds data. */ |
| if (socket->b.n > 0) { |
| luaL_pushresult(&socket->b); |
| return 1; |
| } |
| lua_pushnil(L); |
| lua_pushstring(L, "connection closed."); |
| return 2; |
| |
| connection_empty: |
| |
| if (!notification_new(&hlua->com, &csk_ctx->wake_on_read, hlua->task)) { |
| xref_unlock(&socket->xref, peer); |
| WILL_LJMP(luaL_error(L, "out of memory")); |
| } |
| xref_unlock(&socket->xref, peer); |
| MAY_LJMP(hlua_yieldk(L, 0, 0, hlua_socket_receive_yield, TICK_ETERNITY, 0)); |
| return 0; |
| } |
| |
| /* This Lua function gets two parameters. The first one can be string |
| * or a number. If the string is "*l", the user requires one line. If |
| * the string is "*a", the user requires all the contents of the stream. |
| * If the value is a number, the user require a number of bytes equal |
| * to the value. The default value is "*l" (a line). |
| * |
| * This parameter with a variable type is converted in integer. This |
| * integer takes this values: |
| * -1 : read a line |
| * -2 : read all the stream |
| * >0 : amount of bytes. |
| * |
| * The second parameter is optional. It contains a string that must be |
| * concatenated with the read data. |
| */ |
| __LJMP static int hlua_socket_receive(struct lua_State *L) |
| { |
| int wanted = HLSR_READ_LINE; |
| const char *pattern; |
| int lastarg, type; |
| char *error; |
| size_t len; |
| struct hlua_socket *socket; |
| |
| if (lua_gettop(L) < 1 || lua_gettop(L) > 3) |
| WILL_LJMP(luaL_error(L, "The 'receive' function requires between 1 and 3 arguments.")); |
| |
| socket = MAY_LJMP(hlua_checksocket(L, 1)); |
| |
| /* Check if we run on the same thread than the xreator thread. |
| * We cannot access to the socket if the thread is different. |
| */ |
| if (socket->tid != tid) |
| WILL_LJMP(luaL_error(L, "connect: cannot use socket on other thread")); |
| |
| /* check for pattern. */ |
| if (lua_gettop(L) >= 2) { |
| type = lua_type(L, 2); |
| if (type == LUA_TSTRING) { |
| pattern = lua_tostring(L, 2); |
| if (strcmp(pattern, "*a") == 0) |
| wanted = HLSR_READ_ALL; |
| else if (strcmp(pattern, "*l") == 0) |
| wanted = HLSR_READ_LINE; |
| else { |
| wanted = strtoll(pattern, &error, 10); |
| if (*error != '\0') |
| WILL_LJMP(luaL_error(L, "Unsupported pattern.")); |
| } |
| } |
| else if (type == LUA_TNUMBER) { |
| wanted = lua_tointeger(L, 2); |
| if (wanted < 0) |
| WILL_LJMP(luaL_error(L, "Unsupported size.")); |
| } |
| } |
| |
| /* Set pattern. */ |
| lua_pushinteger(L, wanted); |
| |
| /* Check if we would replace the top by itself. */ |
| if (lua_gettop(L) != 2) |
| lua_replace(L, 2); |
| |
| /* Save index of the top of the stack because since buffers are used, it |
| * may change |
| */ |
| lastarg = lua_gettop(L); |
| |
| /* init buffer, and fill it with prefix. */ |
| luaL_buffinit(L, &socket->b); |
| |
| /* Check prefix. */ |
| if (lastarg >= 3) { |
| if (lua_type(L, 3) != LUA_TSTRING) |
| WILL_LJMP(luaL_error(L, "Expect a 'string' for the prefix")); |
| pattern = lua_tolstring(L, 3, &len); |
| luaL_addlstring(&socket->b, pattern, len); |
| } |
| |
| return __LJMP(hlua_socket_receive_yield(L, 0, 0)); |
| } |
| |
| /* Write the Lua input string in the output buffer. |
| * This function returns a yield if no space is available. |
| */ |
| static int hlua_socket_write_yield(struct lua_State *L,int status, lua_KContext ctx) |
| { |
| struct hlua_socket *socket; |
| struct hlua *hlua; |
| struct hlua_csk_ctx *csk_ctx; |
| struct appctx *appctx; |
| size_t buf_len; |
| const char *buf; |
| int len; |
| int send_len; |
| int sent; |
| struct xref *peer; |
| struct stream *s; |
| struct stconn *sc; |
| |
| /* Get hlua struct, or NULL if we execute from main lua state */ |
| hlua = hlua_gethlua(L); |
| |
| /* Check if this lua stack is schedulable. */ |
| if (!hlua || !hlua->task) |
| WILL_LJMP(luaL_error(L, "The 'write' function is only allowed in " |
| "'frontend', 'backend' or 'task'")); |
| |
| /* Get object */ |
| socket = MAY_LJMP(hlua_checksocket(L, 1)); |
| buf = MAY_LJMP(luaL_checklstring(L, 2, &buf_len)); |
| sent = MAY_LJMP(luaL_checkinteger(L, 3)); |
| |
| /* Check if we run on the same thread than the xreator thread. |
| * We cannot access to the socket if the thread is different. |
| */ |
| if (socket->tid != tid) |
| WILL_LJMP(luaL_error(L, "connect: cannot use socket on other thread")); |
| |
| /* check for connection break. If some data where read, return it. */ |
| peer = xref_get_peer_and_lock(&socket->xref); |
| if (!peer) { |
| lua_pushinteger(L, -1); |
| return 1; |
| } |
| |
| csk_ctx = container_of(peer, struct hlua_csk_ctx, xref); |
| appctx = csk_ctx->appctx; |
| sc = appctx_sc(appctx); |
| s = __sc_strm(sc); |
| |
| /* Check for connection close. */ |
| if (channel_output_closed(&s->req)) { |
| xref_unlock(&socket->xref, peer); |
| lua_pushinteger(L, -1); |
| return 1; |
| } |
| |
| /* Update the input buffer data. */ |
| buf += sent; |
| send_len = buf_len - sent; |
| |
| /* All the data are sent. */ |
| if (sent >= buf_len) { |
| xref_unlock(&socket->xref, peer); |
| return 1; /* Implicitly return the length sent. */ |
| } |
| |
| /* Check if the buffer is available because HAProxy doesn't allocate |
| * the request buffer if its not required. |
| */ |
| if (s->req.buf.size == 0) { |
| if (!sc_alloc_ibuf(sc, &appctx->buffer_wait)) |
| goto hlua_socket_write_yield_return; |
| } |
| |
| /* Check for available space. */ |
| len = b_room(&s->req.buf); |
| if (len <= 0) { |
| goto hlua_socket_write_yield_return; |
| } |
| |
| /* send data */ |
| if (len < send_len) |
| send_len = len; |
| len = ci_putblk(&s->req, buf, send_len); |
| |
| /* "Not enough space" (-1), "Buffer too little to contain |
| * the data" (-2) are not expected because the available length |
| * is tested. |
| * Other unknown error are also not expected. |
| */ |
| if (len <= 0) { |
| if (len == -1) |
| s->req.flags |= CF_WAKE_WRITE; |
| |
| MAY_LJMP(hlua_socket_close_helper(L)); |
| lua_pop(L, 1); |
| lua_pushinteger(L, -1); |
| xref_unlock(&socket->xref, peer); |
| return 1; |
| } |
| |
| /* update buffers. */ |
| appctx_wakeup(appctx); |
| |
| /* Update length sent. */ |
| lua_pop(L, 1); |
| lua_pushinteger(L, sent + len); |
| |
| /* All the data buffer is sent ? */ |
| if (sent + len >= buf_len) { |
| xref_unlock(&socket->xref, peer); |
| return 1; |
| } |
| |
| hlua_socket_write_yield_return: |
| if (!notification_new(&hlua->com, &csk_ctx->wake_on_write, hlua->task)) { |
| xref_unlock(&socket->xref, peer); |
| WILL_LJMP(luaL_error(L, "out of memory")); |
| } |
| xref_unlock(&socket->xref, peer); |
| MAY_LJMP(hlua_yieldk(L, 0, 0, hlua_socket_write_yield, TICK_ETERNITY, 0)); |
| return 0; |
| } |
| |
| /* This function initiate the send of data. It just check the input |
| * parameters and push an integer in the Lua stack that contain the |
| * amount of data written to the buffer. This is used by the function |
| * "hlua_socket_write_yield" that can yield. |
| * |
| * The Lua function gets between 3 and 4 parameters. The first one is |
| * the associated object. The second is a string buffer. The third is |
| * a facultative integer that represents where is the buffer position |
| * of the start of the data that can send. The first byte is the |
| * position "1". The default value is "1". The fourth argument is a |
| * facultative integer that represents where is the buffer position |
| * of the end of the data that can send. The default is the last byte. |
| */ |
| static int hlua_socket_send(struct lua_State *L) |
| { |
| int i; |
| int j; |
| const char *buf; |
| size_t buf_len; |
| |
| /* Check number of arguments. */ |
| if (lua_gettop(L) < 2 || lua_gettop(L) > 4) |
| WILL_LJMP(luaL_error(L, "'send' needs between 2 and 4 arguments")); |
| |
| /* Get the string. */ |
| buf = MAY_LJMP(luaL_checklstring(L, 2, &buf_len)); |
| |
| /* Get and check j. */ |
| if (lua_gettop(L) == 4) { |
| j = MAY_LJMP(luaL_checkinteger(L, 4)); |
| if (j < 0) |
| j = buf_len + j + 1; |
| if (j > buf_len) |
| j = buf_len + 1; |
| lua_pop(L, 1); |
| } |
| else |
| j = buf_len; |
| |
| /* Get and check i. */ |
| if (lua_gettop(L) == 3) { |
| i = MAY_LJMP(luaL_checkinteger(L, 3)); |
| if (i < 0) |
| i = buf_len + i + 1; |
| if (i > buf_len) |
| i = buf_len + 1; |
| lua_pop(L, 1); |
| } else |
| i = 1; |
| |
| /* Check bth i and j. */ |
| if (i > j) { |
| lua_pushinteger(L, 0); |
| return 1; |
| } |
| if (i == 0 && j == 0) { |
| lua_pushinteger(L, 0); |
| return 1; |
| } |
| if (i == 0) |
| i = 1; |
| if (j == 0) |
| j = 1; |
| |
| /* Pop the string. */ |
| lua_pop(L, 1); |
| |
| /* Update the buffer length. */ |
| buf += i - 1; |
| buf_len = j - i + 1; |
| lua_pushlstring(L, buf, buf_len); |
| |
| /* This unsigned is used to remember the amount of sent data. */ |
| lua_pushinteger(L, 0); |
| |
| return MAY_LJMP(hlua_socket_write_yield(L, 0, 0)); |
| } |
| |
| #define SOCKET_INFO_MAX_LEN sizeof("[0000:0000:0000:0000:0000:0000:0000:0000]:12345") |
| __LJMP static inline int hlua_socket_info(struct lua_State *L, const struct sockaddr_storage *addr) |
| { |
| static char buffer[SOCKET_INFO_MAX_LEN]; |
| int ret; |
| int len; |
| char *p; |
| |
| ret = addr_to_str(addr, buffer+1, SOCKET_INFO_MAX_LEN-1); |
| if (ret <= 0) { |
| lua_pushnil(L); |
| return 1; |
| } |
| |
| if (ret == AF_UNIX) { |
| lua_pushstring(L, buffer+1); |
| return 1; |
| } |
| else if (ret == AF_INET6) { |
| buffer[0] = '['; |
| len = strlen(buffer); |
| buffer[len] = ']'; |
| len++; |
| buffer[len] = ':'; |
| len++; |
| p = buffer; |
| } |
| else if (ret == AF_INET) { |
| p = buffer + 1; |
| len = strlen(p); |
| p[len] = ':'; |
| len++; |
| } |
| else { |
| lua_pushnil(L); |
| return 1; |
| } |
| |
| if (port_to_str(addr, p + len, SOCKET_INFO_MAX_LEN-1 - len) <= 0) { |
| lua_pushnil(L); |
| return 1; |
| } |
| |
| lua_pushstring(L, p); |
| return 1; |
| } |
| |
| /* Returns information about the peer of the connection. */ |
| __LJMP static int hlua_socket_getpeername(struct lua_State *L) |
| { |
| struct hlua_socket *socket; |
| struct xref *peer; |
| struct appctx *appctx; |
| struct stconn *sc; |
| const struct sockaddr_storage *dst; |
| int ret; |
| |
| MAY_LJMP(check_args(L, 1, "getpeername")); |
| |
| socket = MAY_LJMP(hlua_checksocket(L, 1)); |
| |
| /* Check if we run on the same thread than the xreator thread. |
| * We cannot access to the socket if the thread is different. |
| */ |
| if (socket->tid != tid) |
| WILL_LJMP(luaL_error(L, "connect: cannot use socket on other thread")); |
| |
| /* check for connection break. If some data where read, return it. */ |
| peer = xref_get_peer_and_lock(&socket->xref); |
| if (!peer) { |
| lua_pushnil(L); |
| return 1; |
| } |
| |
| appctx = container_of(peer, struct hlua_csk_ctx, xref)->appctx; |
| sc = appctx_sc(appctx); |
| dst = sc_dst(sc_opposite(sc)); |
| if (!dst) { |
| xref_unlock(&socket->xref, peer); |
| lua_pushnil(L); |
| return 1; |
| } |
| |
| ret = MAY_LJMP(hlua_socket_info(L, dst)); |
| xref_unlock(&socket->xref, peer); |
| return ret; |
| } |
| |
| /* Returns information about my connection side. */ |
| static int hlua_socket_getsockname(struct lua_State *L) |
| { |
| struct hlua_socket *socket; |
| struct connection *conn; |
| struct appctx *appctx; |
| struct xref *peer; |
| struct stream *s; |
| int ret; |
| |
| MAY_LJMP(check_args(L, 1, "getsockname")); |
| |
| socket = MAY_LJMP(hlua_checksocket(L, 1)); |
| |
| /* Check if we run on the same thread than the xreator thread. |
| * We cannot access to the socket if the thread is different. |
| */ |
| if (socket->tid != tid) |
| WILL_LJMP(luaL_error(L, "connect: cannot use socket on other thread")); |
| |
| /* check for connection break. If some data where read, return it. */ |
| peer = xref_get_peer_and_lock(&socket->xref); |
| if (!peer) { |
| lua_pushnil(L); |
| return 1; |
| } |
| |
| appctx = container_of(peer, struct hlua_csk_ctx, xref)->appctx; |
| s = appctx_strm(appctx); |
| |
| conn = sc_conn(s->scb); |
| if (!conn || !conn_get_src(conn)) { |
| xref_unlock(&socket->xref, peer); |
| lua_pushnil(L); |
| return 1; |
| } |
| |
| ret = hlua_socket_info(L, conn->src); |
| xref_unlock(&socket->xref, peer); |
| return ret; |
| } |
| |
| /* This struct define the applet. */ |
| static struct applet update_applet = { |
| .obj_type = OBJ_TYPE_APPLET, |
| .name = "<LUA_TCP>", |
| .fct = hlua_socket_handler, |
| .init = hlua_socket_init, |
| .release = hlua_socket_release, |
| }; |
| |
| __LJMP static int hlua_socket_connect_yield(struct lua_State *L, int status, lua_KContext ctx) |
| { |
| struct hlua_socket *socket = MAY_LJMP(hlua_checksocket(L, 1)); |
| struct hlua *hlua; |
| struct xref *peer; |
| struct hlua_csk_ctx *csk_ctx; |
| struct appctx *appctx; |
| struct stream *s; |
| |
| /* Get hlua struct, or NULL if we execute from main lua state */ |
| hlua = hlua_gethlua(L); |
| if (!hlua) |
| return 0; |
| |
| /* Check if we run on the same thread than the xreator thread. |
| * We cannot access to the socket if the thread is different. |
| */ |
| if (socket->tid != tid) |
| WILL_LJMP(luaL_error(L, "connect: cannot use socket on other thread")); |
| |
| /* check for connection break. If some data where read, return it. */ |
| peer = xref_get_peer_and_lock(&socket->xref); |
| if (!peer) { |
| lua_pushnil(L); |
| lua_pushstring(L, "Can't connect"); |
| return 2; |
| } |
| |
| csk_ctx = container_of(peer, struct hlua_csk_ctx, xref); |
| appctx = csk_ctx->appctx; |
| s = appctx_strm(appctx); |
| |
| /* Check if we run on the same thread than the xreator thread. |
| * We cannot access to the socket if the thread is different. |
| */ |
| if (socket->tid != tid) { |
| xref_unlock(&socket->xref, peer); |
| WILL_LJMP(luaL_error(L, "connect: cannot use socket on other thread")); |
| } |
| |
| /* Check for connection close. */ |
| if (!hlua || channel_output_closed(&s->req)) { |
| xref_unlock(&socket->xref, peer); |
| lua_pushnil(L); |
| lua_pushstring(L, "Can't connect"); |
| return 2; |
| } |
| |
| appctx = __sc_appctx(s->scf); |
| |
| /* Check for connection established. */ |
| if (csk_ctx->connected) { |
| xref_unlock(&socket->xref, peer); |
| lua_pushinteger(L, 1); |
| return 1; |
| } |
| |
| if (!notification_new(&hlua->com, &csk_ctx->wake_on_write, hlua->task)) { |
| xref_unlock(&socket->xref, peer); |
| WILL_LJMP(luaL_error(L, "out of memory error")); |
| } |
| xref_unlock(&socket->xref, peer); |
| MAY_LJMP(hlua_yieldk(L, 0, 0, hlua_socket_connect_yield, TICK_ETERNITY, 0)); |
| return 0; |
| } |
| |
| /* This function fail or initite the connection. */ |
| __LJMP static int hlua_socket_connect(struct lua_State *L) |
| { |
| struct hlua_socket *socket; |
| int port = -1; |
| const char *ip; |
| struct hlua *hlua; |
| struct hlua_csk_ctx *csk_ctx; |
| struct appctx *appctx; |
| int low, high; |
| struct sockaddr_storage *addr; |
| struct xref *peer; |
| struct stconn *sc; |
| struct stream *s; |
| |
| if (lua_gettop(L) < 2) |
| WILL_LJMP(luaL_error(L, "connect: need at least 2 arguments")); |
| |
| /* Get args. */ |
| socket = MAY_LJMP(hlua_checksocket(L, 1)); |
| |
| /* Check if we run on the same thread than the xreator thread. |
| * We cannot access to the socket if the thread is different. |
| */ |
| if (socket->tid != tid) |
| WILL_LJMP(luaL_error(L, "connect: cannot use socket on other thread")); |
| |
| ip = MAY_LJMP(luaL_checkstring(L, 2)); |
| if (lua_gettop(L) >= 3) { |
| luaL_Buffer b; |
| port = MAY_LJMP(luaL_checkinteger(L, 3)); |
| |
| /* Force the ip to end with a colon, to support IPv6 addresses |
| * that are not enclosed within square brackets. |
| */ |
| if (port > 0) { |
| luaL_buffinit(L, &b); |
| luaL_addstring(&b, ip); |
| luaL_addchar(&b, ':'); |
| luaL_pushresult(&b); |
| ip = lua_tolstring(L, lua_gettop(L), NULL); |
| } |
| } |
| |
| /* check for connection break. If some data where read, return it. */ |
| peer = xref_get_peer_and_lock(&socket->xref); |
| if (!peer) { |
| lua_pushnil(L); |
| return 1; |
| } |
| |
| /* Parse ip address. */ |
| addr = str2sa_range(ip, NULL, &low, &high, NULL, NULL, NULL, NULL, NULL, PA_O_PORT_OK | PA_O_STREAM); |
| if (!addr) { |
| xref_unlock(&socket->xref, peer); |
| WILL_LJMP(luaL_error(L, "connect: cannot parse destination address '%s'", ip)); |
| } |
| |
| /* Set port. */ |
| if (low == 0) { |
| if (addr->ss_family == AF_INET) { |
| if (port == -1) { |
| xref_unlock(&socket->xref, peer); |
| WILL_LJMP(luaL_error(L, "connect: port missing")); |
| } |
| ((struct sockaddr_in *)addr)->sin_port = htons(port); |
| } else if (addr->ss_family == AF_INET6) { |
| if (port == -1) { |
| xref_unlock(&socket->xref, peer); |
| WILL_LJMP(luaL_error(L, "connect: port missing")); |
| } |
| ((struct sockaddr_in6 *)addr)->sin6_port = htons(port); |
| } |
| } |
| |
| csk_ctx = container_of(peer, struct hlua_csk_ctx, xref); |
| appctx = csk_ctx->appctx; |
| sc = appctx_sc(appctx); |
| s = __sc_strm(sc); |
| |
| if (!sockaddr_alloc(&sc_opposite(sc)->dst, addr, sizeof(*addr))) { |
| xref_unlock(&socket->xref, peer); |
| WILL_LJMP(luaL_error(L, "connect: internal error")); |
| } |
| |
| /* Get hlua struct, or NULL if we execute from main lua state */ |
| hlua = hlua_gethlua(L); |
| if (!hlua) |
| return 0; |
| |
| /* inform the stream that we want to be notified whenever the |
| * connection completes. |
| */ |
| applet_need_more_data(appctx); |
| applet_have_more_data(appctx); |
| appctx_wakeup(appctx); |
| |
| hlua->gc_count++; |
| |
| if (!notification_new(&hlua->com, &csk_ctx->wake_on_write, hlua->task)) { |
| xref_unlock(&socket->xref, peer); |
| WILL_LJMP(luaL_error(L, "out of memory")); |
| } |
| xref_unlock(&socket->xref, peer); |
| |
| task_wakeup(s->task, TASK_WOKEN_INIT); |
| /* Return yield waiting for connection. */ |
| |
| MAY_LJMP(hlua_yieldk(L, 0, 0, hlua_socket_connect_yield, TICK_ETERNITY, 0)); |
| |
| return 0; |
| } |
| |
| #ifdef USE_OPENSSL |
| __LJMP static int hlua_socket_connect_ssl(struct lua_State *L) |
| { |
| struct hlua_socket *socket; |
| struct xref *peer; |
| struct stream *s; |
| |
| MAY_LJMP(check_args(L, 3, "connect_ssl")); |
| socket = MAY_LJMP(hlua_checksocket(L, 1)); |
| |
| /* check for connection break. If some data where read, return it. */ |
| peer = xref_get_peer_and_lock(&socket->xref); |
| if (!peer) { |
| lua_pushnil(L); |
| return 1; |
| } |
| |
| s = appctx_strm(container_of(peer, struct hlua_csk_ctx, xref)->appctx); |
| |
| s->target = &socket_ssl->obj_type; |
| xref_unlock(&socket->xref, peer); |
| return MAY_LJMP(hlua_socket_connect(L)); |
| } |
| #endif |
| |
| __LJMP static int hlua_socket_setoption(struct lua_State *L) |
| { |
| return 0; |
| } |
| |
| __LJMP static int hlua_socket_settimeout(struct lua_State *L) |
| { |
| struct hlua_socket *socket; |
| int tmout; |
| double dtmout; |
| struct xref *peer; |
| struct stream *s; |
| |
| MAY_LJMP(check_args(L, 2, "settimeout")); |
| |
| socket = MAY_LJMP(hlua_checksocket(L, 1)); |
| |
| /* convert the timeout to millis */ |
| dtmout = MAY_LJMP(luaL_checknumber(L, 2)) * 1000; |
| |
| /* Check for negative values */ |
| if (dtmout < 0) |
| WILL_LJMP(luaL_error(L, "settimeout: cannot set negatives values")); |
| |
| if (dtmout > INT_MAX) /* overflow check */ |
| WILL_LJMP(luaL_error(L, "settimeout: cannot set values larger than %d ms", INT_MAX)); |
| |
| tmout = MS_TO_TICKS((int)dtmout); |
| if (tmout == 0) |
| tmout++; /* very small timeouts are adjusted to a minimum of 1ms */ |
| |
| /* Check if we run on the same thread than the xreator thread. |
| * We cannot access to the socket if the thread is different. |
| */ |
| if (socket->tid != tid) |
| WILL_LJMP(luaL_error(L, "connect: cannot use socket on other thread")); |
| |
| /* check for connection break. If some data were read, return it. */ |
| peer = xref_get_peer_and_lock(&socket->xref); |
| if (!peer) { |
| hlua_pusherror(L, "socket: not yet initialised, you can't set timeouts."); |
| WILL_LJMP(lua_error(L)); |
| return 0; |
| } |
| |
| s = appctx_strm(container_of(peer, struct hlua_csk_ctx, xref)->appctx); |
| |
| s->sess->fe->timeout.connect = tmout; |
| s->scf->ioto = tmout; |
| s->scb->ioto = tmout; |
| |
| s->task->expire = tick_add_ifset(now_ms, tmout); |
| task_queue(s->task); |
| |
| xref_unlock(&socket->xref, peer); |
| |
| lua_pushinteger(L, 1); |
| return 1; |
| } |
| |
| __LJMP static int hlua_socket_new(lua_State *L) |
| { |
| struct hlua_socket *socket; |
| struct hlua_csk_ctx *ctx; |
| struct appctx *appctx; |
| |
| /* Check stack size. */ |
| if (!lua_checkstack(L, 3)) { |
| hlua_pusherror(L, "socket: full stack"); |
| goto out_fail_conf; |
| } |
| |
| /* Create the object: obj[0] = userdata. */ |
| lua_newtable(L); |
| socket = MAY_LJMP(lua_newuserdata(L, sizeof(*socket))); |
| lua_rawseti(L, -2, 0); |
| memset(socket, 0, sizeof(*socket)); |
| socket->tid = tid; |
| |
| /* Check if the various memory pools are initialized. */ |
| if (!pool_head_stream || !pool_head_buffer) { |
| hlua_pusherror(L, "socket: uninitialized pools."); |
| goto out_fail_conf; |
| } |
| |
| /* Pop a class stream metatable and affect it to the userdata. */ |
| lua_rawgeti(L, LUA_REGISTRYINDEX, class_socket_ref); |
| lua_setmetatable(L, -2); |
| |
| /* Create the applet context */ |
| appctx = appctx_new_here(&update_applet, NULL); |
| if (!appctx) { |
| hlua_pusherror(L, "socket: out of memory"); |
| goto out_fail_conf; |
| } |
| ctx = applet_reserve_svcctx(appctx, sizeof(*ctx)); |
| ctx->connected = 0; |
| ctx->die = 0; |
| LIST_INIT(&ctx->wake_on_write); |
| LIST_INIT(&ctx->wake_on_read); |
| |
| if (appctx_init(appctx) == -1) { |
| hlua_pusherror(L, "socket: fail to init applet."); |
| goto out_fail_appctx; |
| } |
| |
| /* Initialise cross reference between stream and Lua socket object. */ |
| xref_create(&socket->xref, &ctx->xref); |
| return 1; |
| |
| out_fail_appctx: |
| appctx_free_on_early_error(appctx); |
| out_fail_conf: |
| WILL_LJMP(lua_error(L)); |
| return 0; |
| } |
| |
| /* |
| * |
| * |
| * Class Channel |
| * |
| * |
| */ |
| |
| /* Returns the struct hlua_channel join to the class channel in the |
| * stack entry "ud" or throws an argument error. |
| */ |
| __LJMP static struct channel *hlua_checkchannel(lua_State *L, int ud) |
| { |
| return MAY_LJMP(hlua_checkudata(L, ud, class_channel_ref)); |
| } |
| |
| /* Pushes the channel onto the top of the stack. If the stask does not have a |
| * free slots, the function fails and returns 0; |
| */ |
| static int hlua_channel_new(lua_State *L, struct channel *channel) |
| { |
| /* Check stack size. */ |
| if (!lua_checkstack(L, 3)) |
| return 0; |
| |
| lua_newtable(L); |
| lua_pushlightuserdata(L, channel); |
| lua_rawseti(L, -2, 0); |
| |
| /* Pop a class sesison metatable and affect it to the userdata. */ |
| lua_rawgeti(L, LUA_REGISTRYINDEX, class_channel_ref); |
| lua_setmetatable(L, -2); |
| return 1; |
| } |
| |
| /* Helper function returning a filter attached to a channel at the position <ud> |
| * in the stack, filling the current offset and length of the filter. If no |
| * filter is attached, NULL is returned and <offset> and <len> are not |
| * initialized. |
| */ |
| static struct filter *hlua_channel_filter(lua_State *L, int ud, struct channel *chn, size_t *offset, size_t *len) |
| { |
| struct filter *filter = NULL; |
| |
| if (lua_getfield(L, ud, "__filter") == LUA_TLIGHTUSERDATA) { |
| struct hlua_flt_ctx *flt_ctx; |
| |
| filter = lua_touserdata (L, -1); |
| flt_ctx = filter->ctx; |
| if (hlua_filter_from_payload(filter)) { |
| *offset = flt_ctx->cur_off[CHN_IDX(chn)]; |
| *len = flt_ctx->cur_len[CHN_IDX(chn)]; |
| } |
| } |
| |
| lua_pop(L, 1); |
| return filter; |
| } |
| |
| /* Copies <len> bytes of data present in the channel's buffer, starting at the |
| * offset <offset>, and put it in a LUA string variable. It is the caller |
| * responsibility to ensure <len> and <offset> are valid. It always return the |
| * length of the built string. <len> may be 0, in this case, an empty string is |
| * created and 0 is returned. |
| */ |
| static inline int _hlua_channel_dup(struct channel *chn, lua_State *L, size_t offset, size_t len) |
| { |
| size_t block1, block2; |
| luaL_Buffer b; |
| |
| block1 = len; |
| if (block1 > b_contig_data(&chn->buf, b_peek_ofs(&chn->buf, offset))) |
| block1 = b_contig_data(&chn->buf, b_peek_ofs(&chn->buf, offset)); |
| block2 = len - block1; |
| |
| luaL_buffinit(L, &b); |
| luaL_addlstring(&b, b_peek(&chn->buf, offset), block1); |
| if (block2) |
| luaL_addlstring(&b, b_orig(&chn->buf), block2); |
| luaL_pushresult(&b); |
| return len; |
| } |
| |
| /* Inserts the string <str> to the channel's buffer at the offset <offset>. This |
| * function returns -1 if data cannot be copied. Otherwise, it returns the |
| * number of bytes copied. |
| */ |
| static int _hlua_channel_insert(struct channel *chn, lua_State *L, struct ist str, size_t offset) |
| { |
| int ret = 0; |
| |
| /* Nothing to do, just return */ |
| if (unlikely(istlen(str) == 0)) |
| goto end; |
| |
| if (istlen(str) > c_room(chn)) { |
| ret = -1; |
| goto end; |
| } |
| ret = b_insert_blk(&chn->buf, offset, istptr(str), istlen(str)); |
| |
| end: |
| return ret; |
| } |
| |
| /* Removes <len> bytes of data at the absolute position <offset>. |
| */ |
| static void _hlua_channel_delete(struct channel *chn, size_t offset, size_t len) |
| { |
| size_t end = offset + len; |
| |
| if (b_peek(&chn->buf, end) != b_tail(&chn->buf)) |
| b_move(&chn->buf, b_peek_ofs(&chn->buf, end), |
| b_data(&chn->buf) - end, -len); |
| b_sub(&chn->buf, len); |
| } |
| |
| /* Copies input data in the channel's buffer. It is possible to set a specific |
| * offset (0 by default) and a length (all remaining input data starting for the |
| * offset by default). If there is not enough input data and more data can be |
| * received, this function yields. |
| * |
| * From an action, All input data are considered. For a filter, the offset and |
| * the length of input data to consider are retrieved from the filter context. |
| */ |
| __LJMP static int hlua_channel_get_data_yield(lua_State *L, int status, lua_KContext ctx) |
| { |
| struct channel *chn; |
| struct filter *filter; |
| size_t input, output; |
| int offset, len; |
| |
| chn = MAY_LJMP(hlua_checkchannel(L, 1)); |
| |
| output = co_data(chn); |
| input = ci_data(chn); |
| |
| filter = hlua_channel_filter(L, 1, chn, &output, &input); |
| if (filter && !hlua_filter_from_payload(filter)) |
| WILL_LJMP(lua_error(L)); |
| |
| offset = output; |
| if (lua_gettop(L) > 1) { |
| offset = MAY_LJMP(luaL_checkinteger(L, 2)); |
| if (offset < 0) |
| offset = MAX(0, (int)input + offset); |
| offset += output; |
| if (offset < output || offset > input + output) { |
| lua_pushfstring(L, "offset out of range."); |
| WILL_LJMP(lua_error(L)); |
| } |
| } |
| len = output + input - offset; |
| if (lua_gettop(L) == 3) { |
| len = MAY_LJMP(luaL_checkinteger(L, 3)); |
| if (!len) |
| goto dup; |
| if (len == -1) |
| len = global.tune.bufsize; |
| if (len < 0) { |
| lua_pushfstring(L, "length out of range."); |
| WILL_LJMP(lua_error(L)); |
| } |
| } |
| |
| /* Wait for more data if possible if no length was specified and there |
| * is no data or not enough data was received. |
| */ |
| if (!len || offset + len > output + input) { |
| if (!HLUA_CANT_YIELD(hlua_gethlua(L)) && !channel_input_closed(chn) && channel_may_recv(chn)) { |
| /* Yield waiting for more data, as requested */ |
| MAY_LJMP(hlua_yieldk(L, 0, 0, hlua_channel_get_data_yield, TICK_ETERNITY, 0)); |
| } |
| |
| /* Return 'nil' if there is no data and the channel can't receive more data */ |
| if (!len) { |
| lua_pushnil(L); |
| return -1; |
| } |
| |
| /* Otherwise, return all data */ |
| len = output + input - offset; |
| } |
| |
| dup: |
| _hlua_channel_dup(chn, L, offset, len); |
| return 1; |
| } |
| |
| /* Copies the first line (including the trailing LF) of input data in the |
| * channel's buffer. It is possible to set a specific offset (0 by default) and |
| * a length (all remaining input data starting for the offset by default). If |
| * there is not enough input data and more data can be received, the function |
| * yields. If a length is explicitly specified, no more data are |
| * copied. Otherwise, if no LF is found and more data can be received, this |
| * function yields. |
| * |
| * From an action, All input data are considered. For a filter, the offset and |
| * the length of input data to consider are retrieved from the filter context. |
| */ |
| __LJMP static int hlua_channel_get_line_yield(lua_State *L, int status, lua_KContext ctx) |
| { |
| struct channel *chn; |
| struct filter *filter; |
| size_t l, input, output; |
| int offset, len; |
| |
| chn = MAY_LJMP(hlua_checkchannel(L, 1)); |
| output = co_data(chn); |
| input = ci_data(chn); |
| |
| filter = hlua_channel_filter(L, 1, chn, &output, &input); |
| if (filter && !hlua_filter_from_payload(filter)) |
| WILL_LJMP(lua_error(L)); |
| |
| offset = output; |
| if (lua_gettop(L) > 1) { |
| offset = MAY_LJMP(luaL_checkinteger(L, 2)); |
| if (offset < 0) |
| offset = MAX(0, (int)input + offset); |
| offset += output; |
| if (offset < output || offset > input + output) { |
| lua_pushfstring(L, "offset out of range."); |
| WILL_LJMP(lua_error(L)); |
| } |
| } |
| |
| len = output + input - offset; |
| if (lua_gettop(L) == 3) { |
| len = MAY_LJMP(luaL_checkinteger(L, 3)); |
| if (!len) |
| goto dup; |
| if (len == -1) |
| len = global.tune.bufsize; |
| if (len < 0) { |
| lua_pushfstring(L, "length out of range."); |
| WILL_LJMP(lua_error(L)); |
| } |
| } |
| |
| for (l = 0; l < len; l++) { |
| if (l + offset >= output + input) |
| break; |
| if (*(b_peek(&chn->buf, offset + l)) == '\n') { |
| len = l+1; |
| goto dup; |
| } |
| } |
| |
| /* Wait for more data if possible if no line is found and no length was |
| * specified or not enough data was received. |
| */ |
| if (lua_gettop(L) != 3 || offset + len > output + input) { |
| if (!HLUA_CANT_YIELD(hlua_gethlua(L)) && !channel_input_closed(chn) && channel_may_recv(chn)) { |
| /* Yield waiting for more data */ |
| MAY_LJMP(hlua_yieldk(L, 0, 0, hlua_channel_get_line_yield, TICK_ETERNITY, 0)); |
| } |
| |
| /* Return 'nil' if there is no data and the channel can't receive more data */ |
| if (!len) { |
| lua_pushnil(L); |
| return -1; |
| } |
| |
| /* Otherwise, return all data */ |
| len = output + input - offset; |
| } |
| |
| dup: |
| _hlua_channel_dup(chn, L, offset, len); |
| return 1; |
| } |
| |
| /* [ DEPRECATED ] |
| * |
| * Duplicate all input data foud in the channel's buffer. The data are not |
| * removed from the buffer. This function relies on _hlua_channel_dup(). |
| * |
| * From an action, All input data are considered. For a filter, the offset and |
| * the length of input data to consider are retrieved from the filter context. |
| */ |
| __LJMP static int hlua_channel_dup(lua_State *L) |
| { |
| struct channel *chn; |
| struct filter *filter; |
| size_t offset, len; |
| |
| MAY_LJMP(check_args(L, 1, "dup")); |
| chn = MAY_LJMP(hlua_checkchannel(L, 1)); |
| if (IS_HTX_STRM(chn_strm(chn))) { |
| lua_pushfstring(L, "Cannot manipulate HAProxy channels in HTTP mode."); |
| WILL_LJMP(lua_error(L)); |
| } |
| |
| offset = co_data(chn); |
| len = ci_data(chn); |
| |
| filter = hlua_channel_filter(L, 1, chn, &offset, &len); |
| if (filter && !hlua_filter_from_payload(filter)) |
| WILL_LJMP(lua_error(L)); |
| |
| if (!ci_data(chn) && channel_input_closed(chn)) { |
| lua_pushnil(L); |
| return 1; |
| } |
| |
| _hlua_channel_dup(chn, L, offset, len); |
| return 1; |
| } |
| |
| /* [ DEPRECATED ] |
| * |
| * Get all input data foud in the channel's buffer. The data are removed from |
| * the buffer after the copy. This function relies on _hlua_channel_dup() and |
| * _hlua_channel_delete(). |
| * |
| * From an action, All input data are considered. For a filter, the offset and |
| * the length of input data to consider are retrieved from the filter context. |
| */ |
| __LJMP static int hlua_channel_get(lua_State *L) |
| { |
| struct channel *chn; |
| struct filter *filter; |
| size_t offset, len; |
| int ret; |
| |
| MAY_LJMP(check_args(L, 1, "get")); |
| chn = MAY_LJMP(hlua_checkchannel(L, 1)); |
| if (IS_HTX_STRM(chn_strm(chn))) { |
| lua_pushfstring(L, "Cannot manipulate HAProxy channels in HTTP mode."); |
| WILL_LJMP(lua_error(L)); |
| } |
| |
| offset = co_data(chn); |
| len = ci_data(chn); |
| |
| filter = hlua_channel_filter(L, 1, chn, &offset, &len); |
| if (filter && !hlua_filter_from_payload(filter)) |
| WILL_LJMP(lua_error(L)); |
| |
| if (!ci_data(chn) && channel_input_closed(chn)) { |
| lua_pushnil(L); |
| return 1; |
| } |
| |
| ret = _hlua_channel_dup(chn, L, offset, len); |
| _hlua_channel_delete(chn, offset, ret); |
| return 1; |
| } |
| |
| /* This functions consumes and returns one line. If the channel is closed, |
| * and the last data does not contains a final '\n', the data are returned |
| * without the final '\n'. When no more data are available, it returns nil |
| * value. |
| * |
| * From an action, All input data are considered. For a filter, the offset and |
| * the length of input data to consider are retrieved from the filter context. |
| */ |
| __LJMP static int hlua_channel_getline_yield(lua_State *L, int status, lua_KContext ctx) |
| { |
| struct channel *chn; |
| struct filter *filter; |
| size_t l, offset, len; |
| int ret; |
| |
| chn = MAY_LJMP(hlua_checkchannel(L, 1)); |
| |
| offset = co_data(chn); |
| len = ci_data(chn); |
| |
| filter = hlua_channel_filter(L, 1, chn, &offset, &len); |
| if (filter && !hlua_filter_from_payload(filter)) |
| WILL_LJMP(lua_error(L)); |
| |
| if (!ci_data(chn) && channel_input_closed(chn)) { |
| lua_pushnil(L); |
| return 1; |
| } |
| |
| for (l = 0; l < len; l++) { |
| if (*(b_peek(&chn->buf, offset+l)) == '\n') { |
| len = l+1; |
| goto dup; |
| } |
| } |
| |
| if (!HLUA_CANT_YIELD(hlua_gethlua(L)) && !channel_input_closed(chn) && channel_may_recv(chn)) { |
| /* Yield waiting for more data */ |
| MAY_LJMP(hlua_yieldk(L, 0, 0, hlua_channel_getline_yield, TICK_ETERNITY, 0)); |
| } |
| |
| dup: |
| ret = _hlua_channel_dup(chn, L, offset, len); |
| _hlua_channel_delete(chn, offset, ret); |
| return 1; |
| } |
| |
| /* [ DEPRECATED ] |
| * |
| * Check arguments for the function "hlua_channel_getline_yield". |
| */ |
| __LJMP static int hlua_channel_getline(lua_State *L) |
| { |
| struct channel *chn; |
| |
| MAY_LJMP(check_args(L, 1, "getline")); |
| chn = MAY_LJMP(hlua_checkchannel(L, 1)); |
| if (IS_HTX_STRM(chn_strm(chn))) { |
| lua_pushfstring(L, "Cannot manipulate HAProxy channels in HTTP mode."); |
| WILL_LJMP(lua_error(L)); |
| } |
| return MAY_LJMP(hlua_channel_getline_yield(L, 0, 0)); |
| } |
| |
| /* Retrieves a given amount of input data at the given offset. By default all |
| * available input data are returned. The offset may be negactive to start from |
| * the end of input data. The length may be -1 to set it to the maximum buffer |
| * size. |
| */ |
| __LJMP static int hlua_channel_get_data(lua_State *L) |
| { |
| struct channel *chn; |
| |
| if (lua_gettop(L) < 1 || lua_gettop(L) > 3) |
| WILL_LJMP(luaL_error(L, "'data' expects at most 2 arguments")); |
| chn = MAY_LJMP(hlua_checkchannel(L, 1)); |
| if (IS_HTX_STRM(chn_strm(chn))) { |
| lua_pushfstring(L, "Cannot manipulate HAProxy channels in HTTP mode."); |
| WILL_LJMP(lua_error(L)); |
| } |
| return MAY_LJMP(hlua_channel_get_data_yield(L, 0, 0)); |
| } |
| |
| /* Retrieves a given amount of input data at the given offset. By default all |
| * available input data are returned. The offset may be negactive to start from |
| * the end of input data. The length may be -1 to set it to the maximum buffer |
| * size. |
| */ |
| __LJMP static int hlua_channel_get_line(lua_State *L) |
| { |
| struct channel *chn; |
| |
| if (lua_gettop(L) < 1 || lua_gettop(L) > 3) |
| WILL_LJMP(luaL_error(L, "'line' expects at most 2 arguments")); |
| chn = MAY_LJMP(hlua_checkchannel(L, 1)); |
| if (IS_HTX_STRM(chn_strm(chn))) { |
| lua_pushfstring(L, "Cannot manipulate HAProxy channels in HTTP mode."); |
| WILL_LJMP(lua_error(L)); |
| } |
| return MAY_LJMP(hlua_channel_get_line_yield(L, 0, 0)); |
| } |
| |
| /* Appends a string into the input side of channel. It returns the length of the |
| * written string, or -1 if the channel is closed or if the buffer size is too |
| * little for the data. 0 may be returned if nothing is copied. This function |
| * does not yield. |
| * |
| * For a filter, the context is updated on success. |
| */ |
| __LJMP static int hlua_channel_append(lua_State *L) |
| { |
| struct channel *chn; |
| struct filter *filter; |
| const char *str; |
| size_t sz, offset, len; |
| int ret; |
| |
| MAY_LJMP(check_args(L, 2, "append")); |
| chn = MAY_LJMP(hlua_checkchannel(L, 1)); |
| str = MAY_LJMP(luaL_checklstring(L, 2, &sz)); |
| if (IS_HTX_STRM(chn_strm(chn))) { |
| lua_pushfstring(L, "Cannot manipulate HAProxy channels in HTTP mode."); |
| WILL_LJMP(lua_error(L)); |
| } |
| |
| offset = co_data(chn); |
| len = ci_data(chn); |
| |
| filter = hlua_channel_filter(L, 1, chn, &offset, &len); |
| if (filter && !hlua_filter_from_payload(filter)) |
| WILL_LJMP(lua_error(L)); |
| |
| ret = _hlua_channel_insert(chn, L, ist2(str, sz), offset); |
| if (ret > 0 && filter) { |
| struct hlua_flt_ctx *flt_ctx = filter->ctx; |
| |
| flt_update_offsets(filter, chn, ret); |
| flt_ctx->cur_len[CHN_IDX(chn)] += ret; |
| } |
| lua_pushinteger(L, ret); |
| return 1; |
| } |
| |
| /* Prepends a string into the input side of channel. It returns the length of the |
| * written string, or -1 if the channel is closed or if the buffer size is too |
| * little for the data. 0 may be returned if nothing is copied. This function |
| * does not yield. |
| * |
| * For a filter, the context is updated on success. |
| */ |
| __LJMP static int hlua_channel_prepend(lua_State *L) |
| { |
| struct channel *chn; |
| struct filter *filter; |
| const char *str; |
| size_t sz, offset, len; |
| int ret; |
| |
| MAY_LJMP(check_args(L, 2, "prepend")); |
| chn = MAY_LJMP(hlua_checkchannel(L, 1)); |
| str = MAY_LJMP(luaL_checklstring(L, 2, &sz)); |
| if (IS_HTX_STRM(chn_strm(chn))) { |
| lua_pushfstring(L, "Cannot manipulate HAProxy channels in HTTP mode."); |
| WILL_LJMP(lua_error(L)); |
| } |
| |
| offset = co_data(chn); |
| len = ci_data(chn); |
| |
| filter = hlua_channel_filter(L, 1, chn, &offset, &len); |
| if (filter && !hlua_filter_from_payload(filter)) |
| WILL_LJMP(lua_error(L)); |
| |
| ret = _hlua_channel_insert(chn, L, ist2(str, sz), offset); |
| if (ret > 0 && filter) { |
| struct hlua_flt_ctx *flt_ctx = filter->ctx; |
| |
| flt_update_offsets(filter, chn, ret); |
| flt_ctx->cur_len[CHN_IDX(chn)] += ret; |
| } |
| |
| lua_pushinteger(L, ret); |
| return 1; |
| } |
| |
| /* Inserts a given amount of input data at the given offset by a string |
| * content. By default the string is appended in front of input data. It |
| * returns the length of the written string, or -1 if the channel is closed or |
| * if the buffer size is too little for the data. |
| * |
| * For a filter, the context is updated on success. |
| */ |
| __LJMP static int hlua_channel_insert_data(lua_State *L) |
| { |
| struct channel *chn; |
| struct filter *filter; |
| const char *str; |
| size_t sz, input, output; |
| int ret, offset; |
| |
| if (lua_gettop(L) < 2 || lua_gettop(L) > 3) |
| WILL_LJMP(luaL_error(L, "'insert' expects at least 1 argument and at most 2 arguments")); |
| chn = MAY_LJMP(hlua_checkchannel(L, 1)); |
| str = MAY_LJMP(luaL_checklstring(L, 2, &sz)); |
| |
| output = co_data(chn); |
| input = ci_data(chn); |
| |
| filter = hlua_channel_filter(L, 1, chn, &output, &input); |
| if (filter && !hlua_filter_from_payload(filter)) |
| WILL_LJMP(lua_error(L)); |
| |
| offset = output; |
| if (lua_gettop(L) > 2) { |
| offset = MAY_LJMP(luaL_checkinteger(L, 3)); |
| if (offset < 0) |
| offset = MAX(0, (int)input + offset); |
| offset += output; |
| if (offset > output + input) { |
| lua_pushfstring(L, "offset out of range."); |
| WILL_LJMP(lua_error(L)); |
| } |
| } |
| if (IS_HTX_STRM(chn_strm(chn))) { |
| lua_pushfstring(L, "Cannot manipulate HAProxy channels in HTTP mode."); |
| WILL_LJMP(lua_error(L)); |
| } |
| |
| ret = _hlua_channel_insert(chn, L, ist2(str, sz), offset); |
| if (ret > 0 && filter) { |
| struct hlua_flt_ctx *flt_ctx = filter->ctx; |
| |
| flt_update_offsets(filter, chn, ret); |
| flt_ctx->cur_len[CHN_IDX(chn)] += ret; |
| } |
| |
| lua_pushinteger(L, ret); |
| return 1; |
| } |
| /* Replaces a given amount of input data at the given offset by a string |
| * content. By default all remaining data are removed (offset = 0 and len = |
| * -1). It returns the length of the written string, or -1 if the channel is |
| * closed or if the buffer size is too little for the data. |
| * |
| * For a filter, the context is updated on success. |
| */ |
| __LJMP static int hlua_channel_set_data(lua_State *L) |
| { |
| struct channel *chn; |
| struct filter *filter; |
| const char *str; |
| size_t sz, input, output; |
| int ret, offset, len; |
| |
| if (lua_gettop(L) < 2 || lua_gettop(L) > 4) |
| WILL_LJMP(luaL_error(L, "'set' expects at least 1 argument and at most 3 arguments")); |
| chn = MAY_LJMP(hlua_checkchannel(L, 1)); |
| str = MAY_LJMP(luaL_checklstring(L, 2, &sz)); |
| |
| if (IS_HTX_STRM(chn_strm(chn))) { |
| lua_pushfstring(L, "Cannot manipulate HAProxy channels in HTTP mode."); |
| WILL_LJMP(lua_error(L)); |
| } |
| |
| output = co_data(chn); |
| input = ci_data(chn); |
| |
| filter = hlua_channel_filter(L, 1, chn, &output, &input); |
| if (filter && !hlua_filter_from_payload(filter)) |
| WILL_LJMP(lua_error(L)); |
| |
| offset = output; |
| if (lua_gettop(L) > 2) { |
| offset = MAY_LJMP(luaL_checkinteger(L, 3)); |
| if (offset < 0) |
| offset = MAX(0, (int)input + offset); |
| offset += output; |
| if (offset < output || offset > input + output) { |
| lua_pushfstring(L, "offset out of range."); |
| WILL_LJMP(lua_error(L)); |
| } |
| } |
| |
| len = output + input - offset; |
| if (lua_gettop(L) == 4) { |
| len = MAY_LJMP(luaL_checkinteger(L, 4)); |
| if (!len) |
| goto set; |
| if (len == -1) |
| len = output + input - offset; |
| if (len < 0 || offset + len > output + input) { |
| lua_pushfstring(L, "length out of range."); |
| WILL_LJMP(lua_error(L)); |
| } |
| } |
| |
| set: |
| /* Be sure we can copied the string once input data will be removed. */ |
| if (sz > c_room(chn) + len) |
| lua_pushinteger(L, -1); |
| else { |
| _hlua_channel_delete(chn, offset, len); |
| ret = _hlua_channel_insert(chn, L, ist2(str, sz), offset); |
| if (filter) { |
| struct hlua_flt_ctx *flt_ctx = filter->ctx; |
| |
| len -= (ret > 0 ? ret : 0); |
| flt_update_offsets(filter, chn, -len); |
| flt_ctx->cur_len[CHN_IDX(chn)] -= len; |
| } |
| |
| lua_pushinteger(L, ret); |
| } |
| return 1; |
| } |
| |
| /* Removes a given amount of input data at the given offset. By default all |
| * input data are removed (offset = 0 and len = -1). It returns the amount of |
| * the removed data. |
| * |
| * For a filter, the context is updated on success. |
| */ |
| __LJMP static int hlua_channel_del_data(lua_State *L) |
| { |
| struct channel *chn; |
| struct filter *filter; |
| size_t input, output; |
| int offset, len; |
| |
| if (lua_gettop(L) < 1 || lua_gettop(L) > 3) |
| WILL_LJMP(luaL_error(L, "'remove' expects at most 2 arguments")); |
| chn = MAY_LJMP(hlua_checkchannel(L, 1)); |
| |
| if (IS_HTX_STRM(chn_strm(chn))) { |
| lua_pushfstring(L, "Cannot manipulate HAProxy channels in HTTP mode."); |
| WILL_LJMP(lua_error(L)); |
| } |
| |
| output = co_data(chn); |
| input = ci_data(chn); |
| |
| filter = hlua_channel_filter(L, 1, chn, &output, &input); |
| if (filter && !hlua_filter_from_payload(filter)) |
| WILL_LJMP(lua_error(L)); |
| |
| offset = output; |
| if (lua_gettop(L) > 1) { |
| offset = MAY_LJMP(luaL_checkinteger(L, 2)); |
| if (offset < 0) |
| offset = MAX(0, (int)input + offset); |
| offset += output; |
| if (offset < output || offset > input + output) { |
| lua_pushfstring(L, "offset out of range."); |
| WILL_LJMP(lua_error(L)); |
| } |
| } |
| |
| len = output + input - offset; |
| if (lua_gettop(L) == 3) { |
| len = MAY_LJMP(luaL_checkinteger(L, 3)); |
| if (!len) |
| goto end; |
| if (len == -1) |
| len = output + input - offset; |
| if (len < 0 || offset + len > output + input) { |
| lua_pushfstring(L, "length out of range."); |
| WILL_LJMP(lua_error(L)); |
| } |
| } |
| |
| _hlua_channel_delete(chn, offset, len); |
| if (filter) { |
| struct hlua_flt_ctx *flt_ctx = filter->ctx; |
| |
| flt_update_offsets(filter, chn, -len); |
| flt_ctx->cur_len[CHN_IDX(chn)] -= len; |
| } |
| |
| end: |
| lua_pushinteger(L, len); |
| return 1; |
| } |
| |
| /* Append data in the output side of the buffer. This data is immediately |
| * sent. The function returns the amount of data written. If the buffer |
| * cannot contain the data, the function yields. The function returns -1 |
| * if the channel is closed. |
| */ |
| __LJMP static int hlua_channel_send_yield(lua_State *L, int status, lua_KContext ctx) |
| { |
| struct channel *chn; |
| struct filter *filter; |
| const char *str; |
| size_t offset, len, sz; |
| int l, ret; |
| struct hlua *hlua; |
| |
| /* Get hlua struct, or NULL if we execute from main lua state */ |
| hlua = hlua_gethlua(L); |
| if (!hlua) { |
| lua_pushnil(L); |
| return 1; |
| } |
| |
| chn = MAY_LJMP(hlua_checkchannel(L, 1)); |
| str = MAY_LJMP(luaL_checklstring(L, 2, &sz)); |
| l = MAY_LJMP(luaL_checkinteger(L, 3)); |
| |
| offset = co_data(chn); |
| len = ci_data(chn); |
| |
| filter = hlua_channel_filter(L, 1, chn, &offset, &len); |
| if (filter && !hlua_filter_from_payload(filter)) |
| WILL_LJMP(lua_error(L)); |
| |
| |
| if (unlikely(channel_output_closed(chn))) { |
| lua_pushinteger(L, -1); |
| return 1; |
| } |
| |
| len = c_room(chn); |
| if (len > sz -l) { |
| if (filter) { |
| lua_pushinteger(L, -1); |
| return 1; |
| } |
| len = sz - l; |
| } |
| |
| ret = _hlua_channel_insert(chn, L, ist2(str, len), offset); |
| if (ret == -1) { |
| lua_pop(L, 1); |
| lua_pushinteger(L, -1); |
| return 1; |
| } |
| if (ret) { |
| if (filter) { |
| struct hlua_flt_ctx *flt_ctx = filter->ctx; |
| |
| |
| flt_update_offsets(filter, chn, ret); |
| FLT_OFF(filter, chn) += ret; |
| flt_ctx->cur_off[CHN_IDX(chn)] += ret; |
| } |
| else |
| c_adv(chn, ret); |
| |
| l += ret; |
| lua_pop(L, 1); |
| lua_pushinteger(L, l); |
| } |
| |
| if (l < sz) { |
| /* Yield only if the channel's output is not empty. |
| * Otherwise it means we cannot add more data. */ |
| if (co_data(chn) == 0 || HLUA_CANT_YIELD(hlua_gethlua(L))) |
| return 1; |
| |
| /* If we are waiting for space in the response buffer, we |
| * must set the flag WAKERESWR. This flag required the task |
| * wake up if any activity is detected on the response buffer. |
| */ |
| if (chn->flags & CF_ISRESP) |
| HLUA_SET_WAKERESWR(hlua); |
| else |
| HLUA_SET_WAKEREQWR(hlua); |
| MAY_LJMP(hlua_yieldk(L, 0, 0, hlua_channel_send_yield, TICK_ETERNITY, 0)); |
| } |
| |
| return 1; |
| } |
| |
| /* Just a wrapper of "_hlua_channel_send". This wrapper permits |
| * yield the LUA process, and resume it without checking the |
| * input arguments. |
| * |
| * This function cannot be called from a filter. |
| */ |
| __LJMP static int hlua_channel_send(lua_State *L) |
| { |
| struct channel *chn; |
| |
| MAY_LJMP(check_args(L, 2, "send")); |
| chn = MAY_LJMP(hlua_checkchannel(L, 1)); |
| if (IS_HTX_STRM(chn_strm(chn))) { |
| lua_pushfstring(L, "Cannot manipulate HAProxy channels in HTTP mode."); |
| WILL_LJMP(lua_error(L)); |
| } |
| lua_pushinteger(L, 0); |
| return MAY_LJMP(hlua_channel_send_yield(L, 0, 0)); |
| } |
| |
| /* This function forward and amount of butes. The data pass from |
| * the input side of the buffer to the output side, and can be |
| * forwarded. This function never fails. |
| * |
| * The Lua function takes an amount of bytes to be forwarded in |
| * input. It returns the number of bytes forwarded. |
| */ |
| __LJMP static int hlua_channel_forward_yield(lua_State *L, int status, lua_KContext ctx) |
| { |
| struct channel *chn; |
| struct filter *filter; |
| size_t offset, len, fwd; |
| int l, max; |
| struct hlua *hlua; |
| |
| /* Get hlua struct, or NULL if we execute from main lua state */ |
| hlua = hlua_gethlua(L); |
| if (!hlua) { |
| lua_pushnil(L); |
| return 1; |
| } |
| |
| chn = MAY_LJMP(hlua_checkchannel(L, 1)); |
| fwd = MAY_LJMP(luaL_checkinteger(L, 2)); |
| l = MAY_LJMP(luaL_checkinteger(L, -1)); |
| |
| offset = co_data(chn); |
| len = ci_data(chn); |
| |
| filter = hlua_channel_filter(L, 1, chn, &offset, &len); |
| if (filter && !hlua_filter_from_payload(filter)) |
| WILL_LJMP(lua_error(L)); |
| |
| max = fwd - l; |
| if (max > len) |
| max = len; |
| |
| if (filter) { |
| struct hlua_flt_ctx *flt_ctx = filter->ctx; |
| |
| FLT_OFF(filter, chn) += max; |
| flt_ctx->cur_off[CHN_IDX(chn)] += max; |
| flt_ctx->cur_len[CHN_IDX(chn)] -= max; |
| } |
| else |
| channel_forward(chn, max); |
| |
| l += max; |
| lua_pop(L, 1); |
| lua_pushinteger(L, l); |
| |
| /* Check if it miss bytes to forward. */ |
| if (l < fwd) { |
| /* The the input channel or the output channel are closed, we |
| * must return the amount of data forwarded. |
| */ |
| if (channel_input_closed(chn) || channel_output_closed(chn) || HLUA_CANT_YIELD(hlua_gethlua(L))) |
| return 1; |
| |
| /* If we are waiting for space data in the response buffer, we |
| * must set the flag WAKERESWR. This flag required the task |
| * wake up if any activity is detected on the response buffer. |
| */ |
| if (chn->flags & CF_ISRESP) |
| HLUA_SET_WAKERESWR(hlua); |
| else |
| HLUA_SET_WAKEREQWR(hlua); |
| |
| /* Otherwise, we can yield waiting for new data in the input side. */ |
| MAY_LJMP(hlua_yieldk(L, 0, 0, hlua_channel_forward_yield, TICK_ETERNITY, 0)); |
| } |
| |
| return 1; |
| } |
| |
| /* Just check the input and prepare the stack for the previous |
| * function "hlua_channel_forward_yield" |
| * |
| * This function cannot be called from a filter. |
| */ |
| __LJMP static int hlua_channel_forward(lua_State *L) |
| { |
| struct channel *chn; |
| |
| MAY_LJMP(check_args(L, 2, "forward")); |
| chn = MAY_LJMP(hlua_checkchannel(L, 1)); |
| if (IS_HTX_STRM(chn_strm(chn))) { |
| lua_pushfstring(L, "Cannot manipulate HAProxy channels in HTTP mode."); |
| WILL_LJMP(lua_error(L)); |
| } |
| lua_pushinteger(L, 0); |
| return MAY_LJMP(hlua_channel_forward_yield(L, 0, 0)); |
| } |
| |
| /* Just returns the number of bytes available in the input |
| * side of the buffer. This function never fails. |
| */ |
| __LJMP static int hlua_channel_get_in_len(lua_State *L) |
| { |
| struct channel *chn; |
| struct filter *filter; |
| size_t output, input; |
| |
| MAY_LJMP(check_args(L, 1, "input")); |
| chn = MAY_LJMP(hlua_checkchannel(L, 1)); |
| |
| output = co_data(chn); |
| input = ci_data(chn); |
| filter = hlua_channel_filter(L, 1, chn, &output, &input); |
| if (filter || !IS_HTX_STRM(chn_strm(chn))) |
| lua_pushinteger(L, input); |
| else { |
| struct htx *htx = htxbuf(&chn->buf); |
| |
| lua_pushinteger(L, htx->data - co_data(chn)); |
| } |
| return 1; |
| } |
| |
| /* Returns true if the channel is full. */ |
| __LJMP static int hlua_channel_is_full(lua_State *L) |
| { |
| struct channel *chn; |
| |
| MAY_LJMP(check_args(L, 1, "is_full")); |
| chn = MAY_LJMP(hlua_checkchannel(L, 1)); |
| /* ignore the reserve, we are not on a producer side (ie in an |
| * applet). |
| */ |
| lua_pushboolean(L, channel_full(chn, 0)); |
| return 1; |
| } |
| |
| /* Returns true if the channel may still receive data. */ |
| __LJMP static int hlua_channel_may_recv(lua_State *L) |
| { |
| struct channel *chn; |
| |
| MAY_LJMP(check_args(L, 1, "may_recv")); |
| chn = MAY_LJMP(hlua_checkchannel(L, 1)); |
| lua_pushboolean(L, (!channel_input_closed(chn) && channel_may_recv(chn))); |
| return 1; |
| } |
| |
| /* Returns true if the channel is the response channel. */ |
| __LJMP static int hlua_channel_is_resp(lua_State *L) |
| { |
| struct channel *chn; |
| |
| MAY_LJMP(check_args(L, 1, "is_resp")); |
| chn = MAY_LJMP(hlua_checkchannel(L, 1)); |
| |
| lua_pushboolean(L, !!(chn->flags & CF_ISRESP)); |
| return 1; |
| } |
| |
| /* Just returns the number of bytes available in the output |
| * side of the buffer. This function never fails. |
| */ |
| __LJMP static int hlua_channel_get_out_len(lua_State *L) |
| { |
| struct channel *chn; |
| size_t output, input; |
| |
| MAY_LJMP(check_args(L, 1, "output")); |
| chn = MAY_LJMP(hlua_checkchannel(L, 1)); |
| |
| output = co_data(chn); |
| input = ci_data(chn); |
| hlua_channel_filter(L, 1, chn, &output, &input); |
| |
| lua_pushinteger(L, output); |
| return 1; |
| } |
| |
| /* |
| * |
| * |
| * Class Fetches |
| * |
| * |
| */ |
| |
| /* Returns a struct hlua_session if the stack entry "ud" is |
| * a class stream, otherwise it throws an error. |
| */ |
| __LJMP static struct hlua_smp *hlua_checkfetches(lua_State *L, int ud) |
| { |
| return MAY_LJMP(hlua_checkudata(L, ud, class_fetches_ref)); |
| } |
| |
| /* This function creates and push in the stack a fetch object according |
| * with a current TXN. |
| */ |
| static int hlua_fetches_new(lua_State *L, struct hlua_txn *txn, unsigned int flags) |
| { |
| struct hlua_smp *hsmp; |
| |
| /* Check stack size. */ |
| if (!lua_checkstack(L, 3)) |
| return 0; |
| |
| /* Create the object: obj[0] = userdata. |
| * Note that the base of the Fetches object is the |
| * transaction object. |
| */ |
| lua_newtable(L); |
| hsmp = lua_newuserdata(L, sizeof(*hsmp)); |
| lua_rawseti(L, -2, 0); |
| |
| hsmp->s = txn->s; |
| hsmp->p = txn->p; |
| hsmp->dir = txn->dir; |
| hsmp->flags = flags; |
| |
| /* Pop a class sesison metatable and affect it to the userdata. */ |
| lua_rawgeti(L, LUA_REGISTRYINDEX, class_fetches_ref); |
| lua_setmetatable(L, -2); |
| |
| return 1; |
| } |
| |
| /* This function is an LUA binding. It is called with each sample-fetch. |
| * It uses closure argument to store the associated sample-fetch. It |
| * returns only one argument or throws an error. An error is thrown |
| * only if an error is encountered during the argument parsing. If |
| * the "sample-fetch" function fails, nil is returned. |
| */ |
| __LJMP static int hlua_run_sample_fetch(lua_State *L) |
| { |
| struct hlua_smp *hsmp; |
| struct sample_fetch *f; |
| struct arg args[ARGM_NBARGS + 1] = {{0}}; |
| int i; |
| struct sample smp; |
| |
| /* Get closure arguments. */ |
| f = lua_touserdata(L, lua_upvalueindex(1)); |
| |
| /* Get traditional arguments. */ |
| hsmp = MAY_LJMP(hlua_checkfetches(L, 1)); |
| |
| /* Check execution authorization. */ |
| if (f->use & SMP_USE_HTTP_ANY && |
| !(hsmp->flags & HLUA_F_MAY_USE_HTTP)) { |
| lua_pushfstring(L, "the sample-fetch '%s' needs an HTTP parser which " |
| "is not available in Lua services", f->kw); |
| WILL_LJMP(lua_error(L)); |
| } |
| |
| /* Get extra arguments. */ |
| for (i = 0; i < lua_gettop(L) - 1; i++) { |
| if (i >= ARGM_NBARGS) |
| break; |
| hlua_lua2arg(L, i + 2, &args[i]); |
| } |
| args[i].type = ARGT_STOP; |
| args[i].data.str.area = NULL; |
| |
| /* Check arguments. */ |
| MAY_LJMP(hlua_lua2arg_check(L, 2, args, f->arg_mask, hsmp->p)); |
| |
| /* Run the special args checker. */ |
| if (f->val_args && !f->val_args(args, NULL)) { |
| lua_pushfstring(L, "error in arguments"); |
| goto error; |
| } |
| |
| /* Initialise the sample. */ |
| memset(&smp, 0, sizeof(smp)); |
| |
| /* Run the sample fetch process. */ |
| smp_set_owner(&smp, hsmp->p, hsmp->s->sess, hsmp->s, hsmp->dir & SMP_OPT_DIR); |
| if (!f->process(args, &smp, f->kw, f->private)) { |
| if (hsmp->flags & HLUA_F_AS_STRING) |
| lua_pushstring(L, ""); |
| else |
| lua_pushnil(L); |
| goto end; |
| } |
| |
| /* Convert the returned sample in lua value. */ |
| if (hsmp->flags & HLUA_F_AS_STRING) |
| hlua_smp2lua_str(L, &smp); |
| else |
| hlua_smp2lua(L, &smp); |
| |
| end: |
| free_args(args); |
| return 1; |
| |
| error: |
| free_args(args); |
| WILL_LJMP(lua_error(L)); |
| return 0; /* Never reached */ |
| } |
| |
| /* |
| * |
| * |
| * Class Converters |
| * |
| * |
| */ |
| |
| /* Returns a struct hlua_session if the stack entry "ud" is |
| * a class stream, otherwise it throws an error. |
| */ |
| __LJMP static struct hlua_smp *hlua_checkconverters(lua_State *L, int ud) |
| { |
| return MAY_LJMP(hlua_checkudata(L, ud, class_converters_ref)); |
| } |
| |
| /* This function creates and push in the stack a Converters object |
| * according with a current TXN. |
| */ |
| static int hlua_converters_new(lua_State *L, struct hlua_txn *txn, unsigned int flags) |
| { |
| struct hlua_smp *hsmp; |
| |
| /* Check stack size. */ |
| if (!lua_checkstack(L, 3)) |
| return 0; |
| |
| /* Create the object: obj[0] = userdata. |
| * Note that the base of the Converters object is the |
| * same than the TXN object. |
| */ |
| lua_newtable(L); |
| hsmp = lua_newuserdata(L, sizeof(*hsmp)); |
| lua_rawseti(L, -2, 0); |
| |
| hsmp->s = txn->s; |
| hsmp->p = txn->p; |
| hsmp->dir = txn->dir; |
| hsmp->flags = flags; |
| |
| /* Pop a class stream metatable and affect it to the table. */ |
| lua_rawgeti(L, LUA_REGISTRYINDEX, class_converters_ref); |
| lua_setmetatable(L, -2); |
| |
| return 1; |
| } |
| |
| /* This function is an LUA binding. It is called with each converter. |
| * It uses closure argument to store the associated converter. It |
| * returns only one argument or throws an error. An error is thrown |
| * only if an error is encountered during the argument parsing. If |
| * the converter function function fails, nil is returned. |
| */ |
| __LJMP static int hlua_run_sample_conv(lua_State *L) |
| { |
| struct hlua_smp *hsmp; |
| struct sample_conv *conv; |
| struct arg args[ARGM_NBARGS + 1] = {{0}}; |
| int i; |
| struct sample smp; |
| |
| /* Get closure arguments. */ |
| conv = lua_touserdata(L, lua_upvalueindex(1)); |
| |
| /* Get traditional arguments. */ |
| hsmp = MAY_LJMP(hlua_checkconverters(L, 1)); |
| |
| /* Get extra arguments. */ |
| for (i = 0; i < lua_gettop(L) - 2; i++) { |
| if (i >= ARGM_NBARGS) |
| break; |
| hlua_lua2arg(L, i + 3, &args[i]); |
| } |
| args[i].type = ARGT_STOP; |
| args[i].data.str.area = NULL; |
| |
| /* Check arguments. */ |
| MAY_LJMP(hlua_lua2arg_check(L, 3, args, conv->arg_mask, hsmp->p)); |
| |
| /* Run the special args checker. */ |
| if (conv->val_args && !conv->val_args(args, conv, "", 0, NULL)) { |
| hlua_pusherror(L, "error in arguments"); |
| goto error; |
| } |
| |
| /* Initialise the sample. */ |
| memset(&smp, 0, sizeof(smp)); |
| if (!hlua_lua2smp(L, 2, &smp)) { |
| hlua_pusherror(L, "error in the input argument"); |
| goto error; |
| } |
| |
| smp_set_owner(&smp, hsmp->p, hsmp->s->sess, hsmp->s, hsmp->dir & SMP_OPT_DIR); |
| |
| /* Apply expected cast. */ |
| if (!sample_casts[smp.data.type][conv->in_type]) { |
| hlua_pusherror(L, "invalid input argument: cannot cast '%s' to '%s'", |
| smp_to_type[smp.data.type], smp_to_type[conv->in_type]); |
| goto error; |
| } |
| if (sample_casts[smp.data.type][conv->in_type] != c_none && |
| !sample_casts[smp.data.type][conv->in_type](&smp)) { |
| hlua_pusherror(L, "error during the input argument casting"); |
| goto error; |
| } |
| |
| /* Run the sample conversion process. */ |
| if (!conv->process(args, &smp, conv->private)) { |
| if (hsmp->flags & HLUA_F_AS_STRING) |
| lua_pushstring(L, ""); |
| else |
| lua_pushnil(L); |
| goto end; |
| } |
| |
| /* Convert the returned sample in lua value. */ |
| if (hsmp->flags & HLUA_F_AS_STRING) |
| hlua_smp2lua_str(L, &smp); |
| else |
| hlua_smp2lua(L, &smp); |
| end: |
| free_args(args); |
| return 1; |
| |
| error: |
| free_args(args); |
| WILL_LJMP(lua_error(L)); |
| return 0; /* Never reached */ |
| } |
| |
| /* |
| * |
| * |
| * Class AppletTCP |
| * |
| * |
| */ |
| |
| /* Returns a struct hlua_txn if the stack entry "ud" is |
| * a class stream, otherwise it throws an error. |
| */ |
| __LJMP static struct hlua_appctx *hlua_checkapplet_tcp(lua_State *L, int ud) |
| { |
| return MAY_LJMP(hlua_checkudata(L, ud, class_applet_tcp_ref)); |
| } |
| |
| /* This function creates and push in the stack an Applet object |
| * according with a current TXN. |
| */ |
| static int hlua_applet_tcp_new(lua_State *L, struct appctx *ctx) |
| { |
| struct hlua_appctx *luactx; |
| struct stream *s = appctx_strm(ctx); |
| struct proxy *p; |
| |
| ALREADY_CHECKED(s); |
| p = s->be; |
| |
| /* Check stack size. */ |
| if (!lua_checkstack(L, 3)) |
| return 0; |
| |
| /* Create the object: obj[0] = userdata. |
| * Note that the base of the Converters object is the |
| * same than the TXN object. |
| */ |
| lua_newtable(L); |
| luactx = lua_newuserdata(L, sizeof(*luactx)); |
| lua_rawseti(L, -2, 0); |
| luactx->appctx = ctx; |
| luactx->htxn.s = s; |
| luactx->htxn.p = p; |
| |
| /* Create the "f" field that contains a list of fetches. */ |
| lua_pushstring(L, "f"); |
| if (!hlua_fetches_new(L, &luactx->htxn, 0)) |
| return 0; |
| lua_settable(L, -3); |
| |
| /* Create the "sf" field that contains a list of stringsafe fetches. */ |
| lua_pushstring(L, "sf"); |
| if (!hlua_fetches_new(L, &luactx->htxn, HLUA_F_AS_STRING)) |
| return 0; |
| lua_settable(L, -3); |
| |
| /* Create the "c" field that contains a list of converters. */ |
| lua_pushstring(L, "c"); |
| if (!hlua_converters_new(L, &luactx->htxn, 0)) |
| return 0; |
| lua_settable(L, -3); |
| |
| /* Create the "sc" field that contains a list of stringsafe converters. */ |
| lua_pushstring(L, "sc"); |
| if (!hlua_converters_new(L, &luactx->htxn, HLUA_F_AS_STRING)) |
| return 0; |
| lua_settable(L, -3); |
| |
| /* Pop a class stream metatable and affect it to the table. */ |
| lua_rawgeti(L, LUA_REGISTRYINDEX, class_applet_tcp_ref); |
| lua_setmetatable(L, -2); |
| |
| return 1; |
| } |
| |
| __LJMP static int hlua_applet_tcp_set_var(lua_State *L) |
| { |
| struct hlua_appctx *luactx; |
| struct stream *s; |
| const char *name; |
| size_t len; |
| struct sample smp; |
| |
| if (lua_gettop(L) < 3 || lua_gettop(L) > 4) |
| WILL_LJMP(luaL_error(L, "'set_var' needs between 3 and 4 arguments")); |
| |
| /* It is useles to retrieve the stream, but this function |
| * runs only in a stream context. |
| */ |
| luactx = MAY_LJMP(hlua_checkapplet_tcp(L, 1)); |
| name = MAY_LJMP(luaL_checklstring(L, 2, &len)); |
| s = luactx->htxn.s; |
| |
| /* Converts the third argument in a sample. */ |
| memset(&smp, 0, sizeof(smp)); |
| hlua_lua2smp(L, 3, &smp); |
| |
| /* Store the sample in a variable. */ |
| smp_set_owner(&smp, s->be, s->sess, s, 0); |
| |
| if (lua_gettop(L) == 4 && lua_toboolean(L, 4)) |
| lua_pushboolean(L, vars_set_by_name_ifexist(name, len, &smp) != 0); |
| else |
| lua_pushboolean(L, vars_set_by_name(name, len, &smp) != 0); |
| |
| return 1; |
| } |
| |
| __LJMP static int hlua_applet_tcp_unset_var(lua_State *L) |
| { |
| struct hlua_appctx *luactx; |
| struct stream *s; |
| const char *name; |
| size_t len; |
| struct sample smp; |
| |
| MAY_LJMP(check_args(L, 2, "unset_var")); |
| |
| /* It is useles to retrieve the stream, but this function |
| * runs only in a stream context. |
| */ |
| luactx = MAY_LJMP(hlua_checkapplet_tcp(L, 1)); |
| name = MAY_LJMP(luaL_checklstring(L, 2, &len)); |
| s = luactx->htxn.s; |
| |
| /* Unset the variable. */ |
| smp_set_owner(&smp, s->be, s->sess, s, 0); |
| lua_pushboolean(L, vars_unset_by_name_ifexist(name, len, &smp) != 0); |
| return 1; |
| } |
| |
| __LJMP static int hlua_applet_tcp_get_var(lua_State *L) |
| { |
| struct hlua_appctx *luactx; |
| struct stream *s; |
| const char *name; |
| size_t len; |
| struct sample smp; |
| |
| MAY_LJMP(check_args(L, 2, "get_var")); |
| |
| /* It is useles to retrieve the stream, but this function |
| * runs only in a stream context. |
| */ |
| luactx = MAY_LJMP(hlua_checkapplet_tcp(L, 1)); |
| name = MAY_LJMP(luaL_checklstring(L, 2, &len)); |
| s = luactx->htxn.s; |
| |
| smp_set_owner(&smp, s->be, s->sess, s, 0); |
| if (!vars_get_by_name(name, len, &smp, NULL)) { |
| lua_pushnil(L); |
| return 1; |
| } |
| |
| return hlua_smp2lua(L, &smp); |
| } |
| |
| __LJMP static int hlua_applet_tcp_set_priv(lua_State *L) |
| { |
| struct hlua_appctx *luactx = MAY_LJMP(hlua_checkapplet_tcp(L, 1)); |
| struct stream *s = luactx->htxn.s; |
| struct hlua *hlua; |
| |
| /* Note that this hlua struct is from the session and not from the applet. */ |
| if (!s->hlua) |
| return 0; |
| hlua = s->hlua; |
| |
| MAY_LJMP(check_args(L, 2, "set_priv")); |
| |
| /* Remove previous value. */ |
| luaL_unref(L, LUA_REGISTRYINDEX, hlua->Mref); |
| |
| /* Get and store new value. */ |
| lua_pushvalue(L, 2); /* Copy the element 2 at the top of the stack. */ |
| hlua->Mref = luaL_ref(L, LUA_REGISTRYINDEX); /* pop the previously pushed value. */ |
| |
| return 0; |
| } |
| |
| __LJMP static int hlua_applet_tcp_get_priv(lua_State *L) |
| { |
| struct hlua_appctx *luactx = MAY_LJMP(hlua_checkapplet_tcp(L, 1)); |
| struct stream *s = luactx->htxn.s; |
| struct hlua *hlua; |
| |
| /* Note that this hlua struct is from the session and not from the applet. */ |
| if (!s->hlua) { |
| lua_pushnil(L); |
| return 1; |
| } |
| hlua = s->hlua; |
| |
| /* Push configuration index in the stack. */ |
| lua_rawgeti(L, LUA_REGISTRYINDEX, hlua->Mref); |
| |
| return 1; |
| } |
| |
| /* If expected data not yet available, it returns a yield. This function |
| * consumes the data in the buffer. It returns a string containing the |
| * data. This string can be empty. |
| */ |
| __LJMP static int hlua_applet_tcp_getline_yield(lua_State *L, int status, lua_KContext ctx) |
| { |
| struct hlua_appctx *luactx = MAY_LJMP(hlua_checkapplet_tcp(L, 1)); |
| struct stconn *sc = appctx_sc(luactx->appctx); |
| int ret; |
| const char *blk1; |
| size_t len1; |
| const char *blk2; |
| size_t len2; |
| |
| /* Read the maximum amount of data available. */ |
| ret = co_getline_nc(sc_oc(sc), &blk1, &len1, &blk2, &len2); |
| |
| /* Data not yet available. return yield. */ |
| if (ret == 0) { |
| applet_need_more_data(luactx->appctx); |
| MAY_LJMP(hlua_yieldk(L, 0, 0, hlua_applet_tcp_getline_yield, TICK_ETERNITY, 0)); |
| } |
| |
| /* End of data: commit the total strings and return. */ |
| if (ret < 0) { |
| luaL_pushresult(&luactx->b); |
| return 1; |
| } |
| |
| /* Ensure that the block 2 length is usable. */ |
| if (ret == 1) |
| len2 = 0; |
| |
| /* don't check the max length read and don't check. */ |
| luaL_addlstring(&luactx->b, blk1, len1); |
| luaL_addlstring(&luactx->b, blk2, len2); |
| |
| /* Consume input channel output buffer data. */ |
| co_skip(sc_oc(sc), len1 + len2); |
| luaL_pushresult(&luactx->b); |
| return 1; |
| } |
| |
| /* Check arguments for the function "hlua_channel_get_yield". */ |
| __LJMP static int hlua_applet_tcp_getline(lua_State *L) |
| { |
| struct hlua_appctx *luactx = MAY_LJMP(hlua_checkapplet_tcp(L, 1)); |
| |
| /* Initialise the string catenation. */ |
| luaL_buffinit(L, &luactx->b); |
| |
| return MAY_LJMP(hlua_applet_tcp_getline_yield(L, 0, 0)); |
| } |
| |
| /* If expected data not yet available, it returns a yield. This function |
| * consumes the data in the buffer. It returns a string containing the |
| * data. This string can be empty. |
| */ |
| __LJMP static int hlua_applet_tcp_recv_yield(lua_State *L, int status, lua_KContext ctx) |
| { |
| struct hlua_appctx *luactx = MAY_LJMP(hlua_checkapplet_tcp(L, 1)); |
| struct stconn *sc = appctx_sc(luactx->appctx); |
| size_t len = MAY_LJMP(luaL_checkinteger(L, 2)); |
| int ret; |
| const char *blk1; |
| size_t len1; |
| const char *blk2; |
| size_t len2; |
| |
| /* Read the maximum amount of data available. */ |
| ret = co_getblk_nc(sc_oc(sc), &blk1, &len1, &blk2, &len2); |
| |
| /* Data not yet available. return yield. */ |
| if (ret == 0) { |
| applet_need_more_data(luactx->appctx); |
| MAY_LJMP(hlua_yieldk(L, 0, 0, hlua_applet_tcp_recv_yield, TICK_ETERNITY, 0)); |
| } |
| |
| /* End of data: commit the total strings and return. */ |
| if (ret < 0) { |
| luaL_pushresult(&luactx->b); |
| return 1; |
| } |
| |
| /* Ensure that the block 2 length is usable. */ |
| if (ret == 1) |
| len2 = 0; |
| |
| if (len == -1) { |
| |
| /* If len == -1, catenate all the data avalaile and |
| * yield because we want to get all the data until |
| * the end of data stream. |
| */ |
| luaL_addlstring(&luactx->b, blk1, len1); |
| luaL_addlstring(&luactx->b, blk2, len2); |
| co_skip(sc_oc(sc), len1 + len2); |
| applet_need_more_data(luactx->appctx); |
| MAY_LJMP(hlua_yieldk(L, 0, 0, hlua_applet_tcp_recv_yield, TICK_ETERNITY, 0)); |
| |
| } else { |
| |
| /* Copy the first block caping to the length required. */ |
| if (len1 > len) |
| len1 = len; |
| luaL_addlstring(&luactx->b, blk1, len1); |
| len -= len1; |
| |
| /* Copy the second block. */ |
| if (len2 > len) |
| len2 = len; |
| luaL_addlstring(&luactx->b, blk2, len2); |
| len -= len2; |
| |
| /* Consume input channel output buffer data. */ |
| co_skip(sc_oc(sc), len1 + len2); |
| |
| /* If there is no other data available, yield waiting for new data. */ |
| if (len > 0) { |
| lua_pushinteger(L, len); |
| lua_replace(L, 2); |
| applet_need_more_data(luactx->appctx); |
| MAY_LJMP(hlua_yieldk(L, 0, 0, hlua_applet_tcp_recv_yield, TICK_ETERNITY, 0)); |
| } |
| |
| /* return the result. */ |
| luaL_pushresult(&luactx->b); |
| return 1; |
| } |
| |
| /* we never execute this */ |
| hlua_pusherror(L, "Lua: internal error"); |
| WILL_LJMP(lua_error(L)); |
| return 0; |
| } |
| |
| /* Check arguments for the function "hlua_channel_get_yield". */ |
| __LJMP static int hlua_applet_tcp_recv(lua_State *L) |
| { |
| struct hlua_appctx *luactx = MAY_LJMP(hlua_checkapplet_tcp(L, 1)); |
| int len = -1; |
| |
| if (lua_gettop(L) > 2) |
| WILL_LJMP(luaL_error(L, "The 'recv' function requires between 1 and 2 arguments.")); |
| if (lua_gettop(L) >= 2) { |
| len = MAY_LJMP(luaL_checkinteger(L, 2)); |
| lua_pop(L, 1); |
| } |
| |
| /* Confirm or set the required length */ |
| lua_pushinteger(L, len); |
| |
| /* Initialise the string catenation. */ |
| luaL_buffinit(L, &luactx->b); |
| |
| return MAY_LJMP(hlua_applet_tcp_recv_yield(L, 0, 0)); |
| } |
| |
| /* Append data in the output side of the buffer. This data is immediately |
| * sent. The function returns the amount of data written. If the buffer |
| * cannot contain the data, the function yields. The function returns -1 |
| * if the channel is closed. |
| */ |
| __LJMP static int hlua_applet_tcp_send_yield(lua_State *L, int status, lua_KContext ctx) |
| { |
| size_t len; |
| struct hlua_appctx *luactx = MAY_LJMP(hlua_checkapplet_tcp(L, 1)); |
| const char *str = MAY_LJMP(luaL_checklstring(L, 2, &len)); |
| int l = MAY_LJMP(luaL_checkinteger(L, 3)); |
| struct stconn *sc = appctx_sc(luactx->appctx); |
| struct channel *chn = sc_ic(sc); |
| int max; |
| |
| /* Get the max amount of data which can write as input in the channel. */ |
| max = channel_recv_max(chn); |
| if (max > (len - l)) |
| max = len - l; |
| |
| /* Copy data. */ |
| ci_putblk(chn, str + l, max); |
| |
| /* update counters. */ |
| l += max; |
| lua_pop(L, 1); |
| lua_pushinteger(L, l); |
| |
| /* If some data is not send, declares the situation to the |
| * applet, and returns a yield. |
| */ |
| if (l < len) { |
| sc_need_room(sc, channel_recv_max(chn) + 1); |
| MAY_LJMP(hlua_yieldk(L, 0, 0, hlua_applet_tcp_send_yield, TICK_ETERNITY, 0)); |
| } |
| |
| return 1; |
| } |
| |
| /* Just a wrapper of "hlua_applet_tcp_send_yield". This wrapper permits |
| * yield the LUA process, and resume it without checking the |
| * input arguments. |
| */ |
| __LJMP static int hlua_applet_tcp_send(lua_State *L) |
| { |
| MAY_LJMP(check_args(L, 2, "send")); |
| lua_pushinteger(L, 0); |
| |
| return MAY_LJMP(hlua_applet_tcp_send_yield(L, 0, 0)); |
| } |
| |
| /* |
| * |
| * |
| * Class AppletHTTP |
| * |
| * |
| */ |
| |
| /* Returns a struct hlua_txn if the stack entry "ud" is |
| * a class stream, otherwise it throws an error. |
| */ |
| __LJMP static struct hlua_appctx *hlua_checkapplet_http(lua_State *L, int ud) |
| { |
| return MAY_LJMP(hlua_checkudata(L, ud, class_applet_http_ref)); |
| } |
| |
| /* This function creates and push in the stack an Applet object |
| * according with a current TXN. |
| * It relies on the caller to have already reserved the room in ctx->svcctx |
| * for the local storage of hlua_http_ctx. |
| */ |
| static int hlua_applet_http_new(lua_State *L, struct appctx *ctx) |
| { |
| struct hlua_http_ctx *http_ctx = ctx->svcctx; |
| struct hlua_appctx *luactx; |
| struct hlua_txn htxn; |
| struct stream *s = appctx_strm(ctx); |
| struct proxy *px = s->be; |
| struct htx *htx; |
| struct htx_blk *blk; |
| struct htx_sl *sl; |
| struct ist path; |
| unsigned long long len = 0; |
| int32_t pos; |
| struct http_uri_parser parser; |
| |
| /* Check stack size. */ |
| if (!lua_checkstack(L, 3)) |
| return 0; |
| |
| /* Create the object: obj[0] = userdata. |
| * Note that the base of the Converters object is the |
| * same than the TXN object. |
| */ |
| lua_newtable(L); |
| luactx = lua_newuserdata(L, sizeof(*luactx)); |
| lua_rawseti(L, -2, 0); |
| luactx->appctx = ctx; |
| http_ctx->status = 200; /* Default status code returned. */ |
| http_ctx->reason = NULL; /* Use default reason based on status */ |
| luactx->htxn.s = s; |
| luactx->htxn.p = px; |
| |
| /* Create the "f" field that contains a list of fetches. */ |
| lua_pushstring(L, "f"); |
| if (!hlua_fetches_new(L, &luactx->htxn, 0)) |
| return 0; |
| lua_settable(L, -3); |
| |
| /* Create the "sf" field that contains a list of stringsafe fetches. */ |
| lua_pushstring(L, "sf"); |
| if (!hlua_fetches_new(L, &luactx->htxn, HLUA_F_AS_STRING)) |
| return 0; |
| lua_settable(L, -3); |
| |
| /* Create the "c" field that contains a list of converters. */ |
| lua_pushstring(L, "c"); |
| if (!hlua_converters_new(L, &luactx->htxn, 0)) |
| return 0; |
| lua_settable(L, -3); |
| |
| /* Create the "sc" field that contains a list of stringsafe converters. */ |
| lua_pushstring(L, "sc"); |
| if (!hlua_converters_new(L, &luactx->htxn, HLUA_F_AS_STRING)) |
| return 0; |
| lua_settable(L, -3); |
| |
| htx = htxbuf(&s->req.buf); |
| blk = htx_get_first_blk(htx); |
| BUG_ON(!blk || htx_get_blk_type(blk) != HTX_BLK_REQ_SL); |
| sl = htx_get_blk_ptr(htx, blk); |
| |
| /* Stores the request method. */ |
| lua_pushstring(L, "method"); |
| lua_pushlstring(L, HTX_SL_REQ_MPTR(sl), HTX_SL_REQ_MLEN(sl)); |
| lua_settable(L, -3); |
| |
| /* Stores the http version. */ |
| lua_pushstring(L, "version"); |
| lua_pushlstring(L, HTX_SL_REQ_VPTR(sl), HTX_SL_REQ_VLEN(sl)); |
| lua_settable(L, -3); |
| |
| /* creates an array of headers. hlua_http_get_headers() crates and push |
| * the array on the top of the stack. |
| */ |
| lua_pushstring(L, "headers"); |
| htxn.s = s; |
| htxn.p = px; |
| htxn.dir = SMP_OPT_DIR_REQ; |
| if (!hlua_http_get_headers(L, &htxn.s->txn->req)) |
| return 0; |
| lua_settable(L, -3); |
| |
| parser = http_uri_parser_init(htx_sl_req_uri(sl)); |
| path = http_parse_path(&parser); |
| if (isttest(path)) { |
| char *p, *q, *end; |
| |
| p = path.ptr; |
| end = istend(path); |
| q = p; |
| while (q < end && *q != '?') |
| q++; |
| |
| /* Stores the request path. */ |
| lua_pushstring(L, "path"); |
| lua_pushlstring(L, p, q - p); |
| lua_settable(L, -3); |
| |
| /* Stores the query string. */ |
| lua_pushstring(L, "qs"); |
| if (*q == '?') |
| q++; |
| lua_pushlstring(L, q, end - q); |
| lua_settable(L, -3); |
| } |
| |
| for (pos = htx_get_first(htx); pos != -1; pos = htx_get_next(htx, pos)) { |
| struct htx_blk *blk = htx_get_blk(htx, pos); |
| enum htx_blk_type type = htx_get_blk_type(blk); |
| |
| if (type == HTX_BLK_TLR || type == HTX_BLK_EOT) |
| break; |
| if (type == HTX_BLK_DATA) |
| len += htx_get_blksz(blk); |
| } |
| if (htx->extra != HTX_UNKOWN_PAYLOAD_LENGTH) |
| len += htx->extra; |
| |
| /* Stores the request path. */ |
| lua_pushstring(L, "length"); |
| lua_pushinteger(L, len); |
| lua_settable(L, -3); |
| |
| /* Create an empty array of HTTP request headers. */ |
| lua_pushstring(L, "response"); |
| lua_newtable(L); |
| lua_settable(L, -3); |
| |
| /* Pop a class stream metatable and affect it to the table. */ |
| lua_rawgeti(L, LUA_REGISTRYINDEX, class_applet_http_ref); |
| lua_setmetatable(L, -2); |
| |
| return 1; |
| } |
| |
| __LJMP static int hlua_applet_http_set_var(lua_State *L) |
| { |
| struct hlua_appctx *luactx; |
| struct stream *s; |
| const char *name; |
| size_t len; |
| struct sample smp; |
| |
| if (lua_gettop(L) < 3 || lua_gettop(L) > 4) |
| WILL_LJMP(luaL_error(L, "'set_var' needs between 3 and 4 arguments")); |
| |
| /* It is useles to retrieve the stream, but this function |
| * runs only in a stream context. |
| */ |
| luactx = MAY_LJMP(hlua_checkapplet_http(L, 1)); |
| name = MAY_LJMP(luaL_checklstring(L, 2, &len)); |
| s = luactx->htxn.s; |
| |
| /* Converts the third argument in a sample. */ |
| memset(&smp, 0, sizeof(smp)); |
| hlua_lua2smp(L, 3, &smp); |
| |
| /* Store the sample in a variable. */ |
| smp_set_owner(&smp, s->be, s->sess, s, 0); |
| |
| if (lua_gettop(L) == 4 && lua_toboolean(L, 4)) |
| lua_pushboolean(L, vars_set_by_name_ifexist(name, len, &smp) != 0); |
| else |
| lua_pushboolean(L, vars_set_by_name(name, len, &smp) != 0); |
| |
| return 1; |
| } |
| |
| __LJMP static int hlua_applet_http_unset_var(lua_State *L) |
| { |
| struct hlua_appctx *luactx; |
| struct stream *s; |
| const char *name; |
| size_t len; |
| struct sample smp; |
| |
| MAY_LJMP(check_args(L, 2, "unset_var")); |
| |
| /* It is useles to retrieve the stream, but this function |
| * runs only in a stream context. |
| */ |
| luactx = MAY_LJMP(hlua_checkapplet_http(L, 1)); |
| name = MAY_LJMP(luaL_checklstring(L, 2, &len)); |
| s = luactx->htxn.s; |
| |
| /* Unset the variable. */ |
| smp_set_owner(&smp, s->be, s->sess, s, 0); |
| lua_pushboolean(L, vars_unset_by_name_ifexist(name, len, &smp) != 0); |
| return 1; |
| } |
| |
| __LJMP static int hlua_applet_http_get_var(lua_State *L) |
| { |
| struct hlua_appctx *luactx; |
| struct stream *s; |
| const char *name; |
| size_t len; |
| struct sample smp; |
| |
| MAY_LJMP(check_args(L, 2, "get_var")); |
| |
| /* It is useles to retrieve the stream, but this function |
| * runs only in a stream context. |
| */ |
| luactx = MAY_LJMP(hlua_checkapplet_http(L, 1)); |
| name = MAY_LJMP(luaL_checklstring(L, 2, &len)); |
| s = luactx->htxn.s; |
| |
| smp_set_owner(&smp, s->be, s->sess, s, 0); |
| if (!vars_get_by_name(name, len, &smp, NULL)) { |
| lua_pushnil(L); |
| return 1; |
| } |
| |
| return hlua_smp2lua(L, &smp); |
| } |
| |
| __LJMP static int hlua_applet_http_set_priv(lua_State *L) |
| { |
| struct hlua_appctx *luactx = MAY_LJMP(hlua_checkapplet_http(L, 1)); |
| struct stream *s = luactx->htxn.s; |
| struct hlua *hlua; |
| |
| /* Note that this hlua struct is from the session and not from the applet. */ |
| if (!s->hlua) |
| return 0; |
| hlua = s->hlua; |
| |
| MAY_LJMP(check_args(L, 2, "set_priv")); |
| |
| /* Remove previous value. */ |
| luaL_unref(L, LUA_REGISTRYINDEX, hlua->Mref); |
| |
| /* Get and store new value. */ |
| lua_pushvalue(L, 2); /* Copy the element 2 at the top of the stack. */ |
| hlua->Mref = luaL_ref(L, LUA_REGISTRYINDEX); /* pop the previously pushed value. */ |
| |
| return 0; |
| } |
| |
| __LJMP static int hlua_applet_http_get_priv(lua_State *L) |
| { |
| struct hlua_appctx *luactx = MAY_LJMP(hlua_checkapplet_http(L, 1)); |
| struct stream *s = luactx->htxn.s; |
| struct hlua *hlua; |
| |
| /* Note that this hlua struct is from the session and not from the applet. */ |
| if (!s->hlua) { |
| lua_pushnil(L); |
| return 1; |
| } |
| hlua = s->hlua; |
| |
| /* Push configuration index in the stack. */ |
| lua_rawgeti(L, LUA_REGISTRYINDEX, hlua->Mref); |
| |
| return 1; |
| } |
| |
| /* If expected data not yet available, it returns a yield. This function |
| * consumes the data in the buffer. It returns a string containing the |
| * data. This string can be empty. |
| */ |
| __LJMP static int hlua_applet_http_getline_yield(lua_State *L, int status, lua_KContext ctx) |
| { |
| struct hlua_appctx *luactx = MAY_LJMP(hlua_checkapplet_http(L, 1)); |
| struct stconn *sc = appctx_sc(luactx->appctx); |
| struct channel *req = sc_oc(sc); |
| struct htx *htx; |
| struct htx_blk *blk; |
| size_t count; |
| int stop = 0; |
| |
| htx = htx_from_buf(&req->buf); |
| count = co_data(req); |
| blk = htx_get_first_blk(htx); |
| |
| while (count && !stop && blk) { |
| enum htx_blk_type type = htx_get_blk_type(blk); |
| uint32_t sz = htx_get_blksz(blk); |
| struct ist v; |
| uint32_t vlen; |
| char *nl; |
| |
| vlen = sz; |
| if (vlen > count) { |
| if (type != HTX_BLK_DATA) |
| break; |
| vlen = count; |
| } |
| |
| switch (type) { |
| case HTX_BLK_UNUSED: |
| break; |
| |
| case HTX_BLK_DATA: |
| v = htx_get_blk_value(htx, blk); |
| v.len = vlen; |
| nl = istchr(v, '\n'); |
| if (nl != NULL) { |
| stop = 1; |
| vlen = nl - v.ptr + 1; |
| } |
| luaL_addlstring(&luactx->b, v.ptr, vlen); |
| break; |
| |
| case HTX_BLK_TLR: |
| case HTX_BLK_EOT: |
| stop = 1; |
| break; |
| |
| default: |
| break; |
| } |
| |
| c_rew(req, vlen); |
| count -= vlen; |
| if (sz == vlen) |
| blk = htx_remove_blk(htx, blk); |
| else { |
| htx_cut_data_blk(htx, blk, vlen); |
| break; |
| } |
| } |
| |
| /* The message was fully consumed and no more data are expected |
| * (EOM flag set). |
| */ |
| if (htx_is_empty(htx) && (sc->flags & SC_FL_EOI)) |
| stop = 1; |
| |
| htx_to_buf(htx, &req->buf); |
| if (!stop) { |
| applet_need_more_data(luactx->appctx); |
| MAY_LJMP(hlua_yieldk(L, 0, 0, hlua_applet_http_getline_yield, TICK_ETERNITY, 0)); |
| } |
| |
| /* return the result. */ |
| luaL_pushresult(&luactx->b); |
| return 1; |
| } |
| |
| |
| /* Check arguments for the function "hlua_channel_get_yield". */ |
| __LJMP static int hlua_applet_http_getline(lua_State *L) |
| { |
| struct hlua_appctx *luactx = MAY_LJMP(hlua_checkapplet_http(L, 1)); |
| |
| /* Initialise the string catenation. */ |
| luaL_buffinit(L, &luactx->b); |
| |
| return MAY_LJMP(hlua_applet_http_getline_yield(L, 0, 0)); |
| } |
| |
| /* If expected data not yet available, it returns a yield. This function |
| * consumes the data in the buffer. It returns a string containing the |
| * data. This string can be empty. |
| */ |
| __LJMP static int hlua_applet_http_recv_yield(lua_State *L, int status, lua_KContext ctx) |
| { |
| struct hlua_appctx *luactx = MAY_LJMP(hlua_checkapplet_http(L, 1)); |
| struct stconn *sc = appctx_sc(luactx->appctx); |
| struct channel *req = sc_oc(sc); |
| struct htx *htx; |
| struct htx_blk *blk; |
| size_t count; |
| int len; |
| |
| htx = htx_from_buf(&req->buf); |
| len = MAY_LJMP(luaL_checkinteger(L, 2)); |
| count = co_data(req); |
| blk = htx_get_head_blk(htx); |
| while (count && len && blk) { |
| enum htx_blk_type type = htx_get_blk_type(blk); |
| uint32_t sz = htx_get_blksz(blk); |
| struct ist v; |
| uint32_t vlen; |
| |
| vlen = sz; |
| if (len > 0 && vlen > len) |
| vlen = len; |
| if (vlen > count) { |
| if (type != HTX_BLK_DATA) |
| break; |
| vlen = count; |
| } |
| |
| switch (type) { |
| case HTX_BLK_UNUSED: |
| break; |
| |
| case HTX_BLK_DATA: |
| v = htx_get_blk_value(htx, blk); |
| luaL_addlstring(&luactx->b, v.ptr, vlen); |
| break; |
| |
| case HTX_BLK_TLR: |
| case HTX_BLK_EOT: |
| len = 0; |
| break; |
| |
| default: |
| break; |
| } |
| |
| c_rew(req, vlen); |
| count -= vlen; |
| if (len > 0) |
| len -= vlen; |
| if (sz == vlen) |
| blk = htx_remove_blk(htx, blk); |
| else { |
| htx_cut_data_blk(htx, blk, vlen); |
| break; |
| } |
| } |
| |
| /* The message was fully consumed and no more data are expected |
| * (EOM flag set). |
| */ |
| if (htx_is_empty(htx) && (sc->flags & SC_FL_EOI)) |
| len = 0; |
| |
| htx_to_buf(htx, &req->buf); |
| |
| /* If we are no other data available, yield waiting for new data. */ |
| if (len) { |
| if (len > 0) { |
| lua_pushinteger(L, len); |
| lua_replace(L, 2); |
| } |
| applet_need_more_data(luactx->appctx); |
| MAY_LJMP(hlua_yieldk(L, 0, 0, hlua_applet_http_recv_yield, TICK_ETERNITY, 0)); |
| } |
| |
| /* return the result. */ |
| luaL_pushresult(&luactx->b); |
| return 1; |
| } |
| |
| /* Check arguments for the function "hlua_channel_get_yield". */ |
| __LJMP static int hlua_applet_http_recv(lua_State *L) |
| { |
| struct hlua_appctx *luactx = MAY_LJMP(hlua_checkapplet_http(L, 1)); |
| int len = -1; |
| |
| /* Check arguments. */ |
| if (lua_gettop(L) > 2) |
| WILL_LJMP(luaL_error(L, "The 'recv' function requires between 1 and 2 arguments.")); |
| if (lua_gettop(L) >= 2) { |
| len = MAY_LJMP(luaL_checkinteger(L, 2)); |
| lua_pop(L, 1); |
| } |
| |
| lua_pushinteger(L, len); |
| |
| /* Initialise the string catenation. */ |
| luaL_buffinit(L, &luactx->b); |
| |
| return MAY_LJMP(hlua_applet_http_recv_yield(L, 0, 0)); |
| } |
| |
| /* Append data in the output side of the buffer. This data is immediately |
| * sent. The function returns the amount of data written. If the buffer |
| * cannot contain the data, the function yields. The function returns -1 |
| * if the channel is closed. |
| */ |
| __LJMP static int hlua_applet_http_send_yield(lua_State *L, int status, lua_KContext ctx) |
| { |
| struct hlua_appctx *luactx = MAY_LJMP(hlua_checkapplet_http(L, 1)); |
| struct stconn *sc = appctx_sc(luactx->appctx); |
| struct channel *res = sc_ic(sc); |
| struct htx *htx = htx_from_buf(&res->buf); |
| const char *data; |
| size_t len; |
| int l = MAY_LJMP(luaL_checkinteger(L, 3)); |
| int max; |
| |
| max = htx_get_max_blksz(htx, channel_htx_recv_max(res, htx)); |
| if (!max) |
| goto snd_yield; |
| |
| data = MAY_LJMP(luaL_checklstring(L, 2, &len)); |
| |
| /* Get the max amount of data which can write as input in the channel. */ |
| if (max > (len - l)) |
| max = len - l; |
| |
| /* Copy data. */ |
| max = htx_add_data(htx, ist2(data + l, max)); |
| channel_add_input(res, max); |
| |
| /* update counters. */ |
| l += max; |
| lua_pop(L, 1); |
| lua_pushinteger(L, l); |
| |
| /* If some data is not send, declares the situation to the |
| * applet, and returns a yield. |
| */ |
| if (l < len) { |
| snd_yield: |
| htx_to_buf(htx, &res->buf); |
| sc_need_room(sc, channel_recv_max(res) + 1); |
| MAY_LJMP(hlua_yieldk(L, 0, 0, hlua_applet_http_send_yield, TICK_ETERNITY, 0)); |
| } |
| |
| htx_to_buf(htx, &res->buf); |
| return 1; |
| } |
| |
| /* Just a wrapper of "hlua_applet_send_yield". This wrapper permits |
| * yield the LUA process, and resume it without checking the |
| * input arguments. |
| */ |
| __LJMP static int hlua_applet_http_send(lua_State *L) |
| { |
| struct hlua_appctx *luactx = MAY_LJMP(hlua_checkapplet_http(L, 1)); |
| struct hlua_http_ctx *http_ctx = luactx->appctx->svcctx; |
| |
| /* We want to send some data. Headers must be sent. */ |
| if (!(http_ctx->flags & APPLET_HDR_SENT)) { |
| hlua_pusherror(L, "Lua: 'send' you must call start_response() before sending data."); |
| WILL_LJMP(lua_error(L)); |
| } |
| |
| /* This integer is used for followinf the amount of data sent. */ |
| lua_pushinteger(L, 0); |
| |
| return MAY_LJMP(hlua_applet_http_send_yield(L, 0, 0)); |
| } |
| |
| __LJMP static int hlua_applet_http_addheader(lua_State *L) |
| { |
| const char *name; |
| int ret; |
| |
| MAY_LJMP(hlua_checkapplet_http(L, 1)); |
| name = MAY_LJMP(luaL_checkstring(L, 2)); |
| MAY_LJMP(luaL_checkstring(L, 3)); |
| |
| /* Push in the stack the "response" entry. */ |
| ret = lua_getfield(L, 1, "response"); |
| if (ret != LUA_TTABLE) { |
| hlua_pusherror(L, "Lua: 'add_header' internal error: AppletHTTP['response'] " |
| "is expected as an array. %s found", lua_typename(L, ret)); |
| WILL_LJMP(lua_error(L)); |
| } |
| |
| /* check if the header is already registered if it is not |
| * the case, register it. |
| */ |
| ret = lua_getfield(L, -1, name); |
| if (ret == LUA_TNIL) { |
| |
| /* Entry not found. */ |
| lua_pop(L, 1); /* remove the nil. The "response" table is the top of the stack. */ |
| |
| /* Insert the new header name in the array in the top of the stack. |
| * It left the new array in the top of the stack. |
| */ |
| lua_newtable(L); |
| lua_pushvalue(L, 2); |
| lua_pushvalue(L, -2); |
| lua_settable(L, -4); |
| |
| } else if (ret != LUA_TTABLE) { |
| |
| /* corruption error. */ |
| hlua_pusherror(L, "Lua: 'add_header' internal error: AppletHTTP['response']['%s'] " |
| "is expected as an array. %s found", name, lua_typename(L, ret)); |
| WILL_LJMP(lua_error(L)); |
| } |
| |
| /* Now the top of thestack is an array of values. We push |
| * the header value as new entry. |
| */ |
| lua_pushvalue(L, 3); |
| ret = lua_rawlen(L, -2); |
| lua_rawseti(L, -2, ret + 1); |
| lua_pushboolean(L, 1); |
| return 1; |
| } |
| |
| __LJMP static int hlua_applet_http_status(lua_State *L) |
| { |
| struct hlua_appctx *luactx = MAY_LJMP(hlua_checkapplet_http(L, 1)); |
| int status = MAY_LJMP(luaL_checkinteger(L, 2)); |
| const char *reason = MAY_LJMP(luaL_optlstring(L, 3, NULL, NULL)); |
| struct hlua_http_ctx *http_ctx = luactx->appctx->svcctx; |
| |
| if (status < 100 || status > 599) { |
| lua_pushboolean(L, 0); |
| return 1; |
| } |
| |
| http_ctx->status = status; |
| http_ctx->reason = reason; |
| lua_pushboolean(L, 1); |
| return 1; |
| } |
| |
| |
| __LJMP static int hlua_applet_http_send_response(lua_State *L) |
| { |
| struct hlua_appctx *luactx = MAY_LJMP(hlua_checkapplet_http(L, 1)); |
| struct hlua_http_ctx *http_ctx = luactx->appctx->svcctx; |
| struct stconn *sc = appctx_sc(luactx->appctx); |
| struct channel *res = sc_ic(sc); |
| struct htx *htx; |
| struct htx_sl *sl; |
| struct h1m h1m; |
| const char *status, *reason; |
| const char *name, *value; |
| size_t nlen, vlen; |
| unsigned int flags; |
| |
| /* Send the message at once. */ |
| htx = htx_from_buf(&res->buf); |
| h1m_init_res(&h1m); |
| |
| /* Use the same http version than the request. */ |
| status = ultoa_r(http_ctx->status, trash.area, trash.size); |
| reason = http_ctx->reason; |
| if (reason == NULL) |
| reason = http_get_reason(http_ctx->status); |
| if (http_ctx->flags & APPLET_HTTP11) { |
| flags = (HTX_SL_F_IS_RESP|HTX_SL_F_VER_11); |
| sl = htx_add_stline(htx, HTX_BLK_RES_SL, flags, ist("HTTP/1.1"), ist(status), ist(reason)); |
| } |
| else { |
| flags = HTX_SL_F_IS_RESP; |
| sl = htx_add_stline(htx, HTX_BLK_RES_SL, flags, ist("HTTP/1.0"), ist(status), ist(reason)); |
| } |
| if (!sl) { |
| hlua_pusherror(L, "Lua applet http '%s': Failed to create response.\n", |
| luactx->appctx->rule->arg.hlua_rule->fcn->name); |
| WILL_LJMP(lua_error(L)); |
| } |
| sl->info.res.status = http_ctx->status; |
| |
| /* Get the array associated to the field "response" in the object AppletHTTP. */ |
| if (lua_getfield(L, 1, "response") != LUA_TTABLE) { |
| hlua_pusherror(L, "Lua applet http '%s': AppletHTTP['response'] missing.\n", |
| luactx->appctx->rule->arg.hlua_rule->fcn->name); |
| WILL_LJMP(lua_error(L)); |
| } |
| |
| /* Browse the list of headers. */ |
| lua_pushnil(L); |
| while(lua_next(L, -2) != 0) { |
| /* We expect a string as -2. */ |
| if (lua_type(L, -2) != LUA_TSTRING) { |
| hlua_pusherror(L, "Lua applet http '%s': AppletHTTP['response'][] element must be a string. got %s.\n", |
| luactx->appctx->rule->arg.hlua_rule->fcn->name, |
| lua_typename(L, lua_type(L, -2))); |
| WILL_LJMP(lua_error(L)); |
| } |
| name = lua_tolstring(L, -2, &nlen); |
| |
| /* We expect an array as -1. */ |
| if (lua_type(L, -1) != LUA_TTABLE) { |
| hlua_pusherror(L, "Lua applet http '%s': AppletHTTP['response']['%s'] element must be an table. got %s.\n", |
| luactx->appctx->rule->arg.hlua_rule->fcn->name, |
| name, |
| lua_typename(L, lua_type(L, -1))); |
| WILL_LJMP(lua_error(L)); |
| } |
| |
| /* Browse the table who is on the top of the stack. */ |
| lua_pushnil(L); |
| while(lua_next(L, -2) != 0) { |
| int id; |
| |
| /* We expect a number as -2. */ |
| if (lua_type(L, -2) != LUA_TNUMBER) { |
| hlua_pusherror(L, "Lua applet http '%s': AppletHTTP['response']['%s'][] element must be a number. got %s.\n", |
| luactx->appctx->rule->arg.hlua_rule->fcn->name, |
| name, |
| lua_typename(L, lua_type(L, -2))); |
| WILL_LJMP(lua_error(L)); |
| } |
| id = lua_tointeger(L, -2); |
| |
| /* We expect a string as -2. */ |
| if (lua_type(L, -1) != LUA_TSTRING) { |
| hlua_pusherror(L, "Lua applet http '%s': AppletHTTP['response']['%s'][%d] element must be a string. got %s.\n", |
| luactx->appctx->rule->arg.hlua_rule->fcn->name, |
| name, id, |
| lua_typename(L, lua_type(L, -1))); |
| WILL_LJMP(lua_error(L)); |
| } |
| value = lua_tolstring(L, -1, &vlen); |
| |
| /* Simple Protocol checks. */ |
| if (isteqi(ist2(name, nlen), ist("transfer-encoding"))) { |
| int ret; |
| |
| ret = h1_parse_xfer_enc_header(&h1m, ist2(value, vlen)); |
| if (ret < 0) { |
| hlua_pusherror(L, "Lua applet http '%s': Invalid '%s' header.\n", |
| luactx->appctx->rule->arg.hlua_rule->fcn->name, |
| name); |
| WILL_LJMP(lua_error(L)); |
| } |
| else if (ret == 0) |
| goto next; /* Skip it */ |
| } |
| else if (isteqi(ist2(name, nlen), ist("content-length"))) { |
| struct ist v = ist2(value, vlen); |
| int ret; |
| |
| ret = h1_parse_cont_len_header(&h1m, &v); |
| if (ret < 0) { |
| hlua_pusherror(L, "Lua applet http '%s': Invalid '%s' header.\n", |
| luactx->appctx->rule->arg.hlua_rule->fcn->name, |
| name); |
| WILL_LJMP(lua_error(L)); |
| } |
| else if (ret == 0) |
| goto next; /* Skip it */ |
| } |
| |
| /* Add a new header */ |
| if (!htx_add_header(htx, ist2(name, nlen), ist2(value, vlen))) { |
| hlua_pusherror(L, "Lua applet http '%s': Failed to add header '%s' in the response.\n", |
| luactx->appctx->rule->arg.hlua_rule->fcn->name, |
| name); |
| WILL_LJMP(lua_error(L)); |
| } |
| next: |
| /* Remove the array from the stack, and get next element with a remaining string. */ |
| lua_pop(L, 1); |
| } |
| |
| /* Remove the array from the stack, and get next element with a remaining string. */ |
| lua_pop(L, 1); |
| } |
| |
| if (h1m.flags & H1_MF_CHNK) |
| h1m.flags &= ~H1_MF_CLEN; |
| if (h1m.flags & (H1_MF_CLEN|H1_MF_CHNK)) |
| h1m.flags |= H1_MF_XFER_LEN; |
| |
| /* Uset HTX start-line flags */ |
| if (h1m.flags & H1_MF_XFER_ENC) |
| flags |= HTX_SL_F_XFER_ENC; |
| if (h1m.flags & H1_MF_XFER_LEN) { |
| flags |= HTX_SL_F_XFER_LEN; |
| if (h1m.flags & H1_MF_CHNK) |
| flags |= HTX_SL_F_CHNK; |
| else if (h1m.flags & H1_MF_CLEN) |
| flags |= HTX_SL_F_CLEN; |
| if (h1m.body_len == 0) |
| flags |= HTX_SL_F_BODYLESS; |
| } |
| sl->flags |= flags; |
| |
| /* If we don't have a content-length set, and the HTTP version is 1.1 |
| * and the status code implies the presence of a message body, we must |
| * announce a transfer encoding chunked. This is required by haproxy |
| * for the keepalive compliance. If the applet announces a transfer-encoding |
| * chunked itself, don't do anything. |
| */ |
| if ((flags & (HTX_SL_F_VER_11|HTX_SL_F_XFER_LEN)) == HTX_SL_F_VER_11 && |
| http_ctx->status >= 200 && http_ctx->status != 204 && http_ctx->status != 304) { |
| /* Add a new header */ |
| sl->flags |= (HTX_SL_F_XFER_ENC|H1_MF_CHNK|H1_MF_XFER_LEN); |
| if (!htx_add_header(htx, ist("transfer-encoding"), ist("chunked"))) { |
| hlua_pusherror(L, "Lua applet http '%s': Failed to add header 'transfer-encoding' in the response.\n", |
| luactx->appctx->rule->arg.hlua_rule->fcn->name); |
| WILL_LJMP(lua_error(L)); |
| } |
| } |
| |
| /* Finalize headers. */ |
| if (!htx_add_endof(htx, HTX_BLK_EOH)) { |
| hlua_pusherror(L, "Lua applet http '%s': Failed create the response.\n", |
| luactx->appctx->rule->arg.hlua_rule->fcn->name); |
| WILL_LJMP(lua_error(L)); |
| } |
| |
| if (htx_used_space(htx) > b_size(&res->buf) - global.tune.maxrewrite) { |
| b_reset(&res->buf); |
| hlua_pusherror(L, "Lua: 'start_response': response header block too big"); |
| WILL_LJMP(lua_error(L)); |
| } |
| |
| htx_to_buf(htx, &res->buf); |
| channel_add_input(res, htx->data); |
| |
| /* Headers sent, set the flag. */ |
| http_ctx->flags |= APPLET_HDR_SENT; |
| return 0; |
| |
| } |
| /* We will build the status line and the headers of the HTTP response. |
| * We will try send at once if its not possible, we give back the hand |
| * waiting for more room. |
| */ |
| __LJMP static int hlua_applet_http_start_response_yield(lua_State *L, int status, lua_KContext ctx) |
| { |
| struct hlua_appctx *luactx = MAY_LJMP(hlua_checkapplet_http(L, 1)); |
| struct stconn *sc = appctx_sc(luactx->appctx); |
| struct channel *res = sc_ic(sc); |
| |
| if (co_data(res)) { |
| sc_need_room(sc, -1); |
| MAY_LJMP(hlua_yieldk(L, 0, 0, hlua_applet_http_start_response_yield, TICK_ETERNITY, 0)); |
| } |
| return MAY_LJMP(hlua_applet_http_send_response(L)); |
| } |
| |
| |
| __LJMP static int hlua_applet_http_start_response(lua_State *L) |
| { |
| return MAY_LJMP(hlua_applet_http_start_response_yield(L, 0, 0)); |
| } |
| |
| /* |
| * |
| * |
| * Class HTTP |
| * |
| * |
| */ |
| |
| /* Returns a struct hlua_txn if the stack entry "ud" is |
| * a class stream, otherwise it throws an error. |
| */ |
| __LJMP static struct hlua_txn *hlua_checkhttp(lua_State *L, int ud) |
| { |
| return MAY_LJMP(hlua_checkudata(L, ud, class_http_ref)); |
| } |
| |
| /* This function creates and push in the stack a HTTP object |
| * according with a current TXN. |
| */ |
| static int hlua_http_new(lua_State *L, struct hlua_txn *txn) |
| { |
| struct hlua_txn *htxn; |
| |
| /* Check stack size. */ |
| if (!lua_checkstack(L, 3)) |
| return 0; |
| |
| /* Create the object: obj[0] = userdata. |
| * Note that the base of the Converters object is the |
| * same than the TXN object. |
| */ |
| lua_newtable(L); |
| htxn = lua_newuserdata(L, sizeof(*htxn)); |
| lua_rawseti(L, -2, 0); |
| |
| htxn->s = txn->s; |
| htxn->p = txn->p; |
| htxn->dir = txn->dir; |
| htxn->flags = txn->flags; |
| |
| /* Pop a class stream metatable and affect it to the table. */ |
| lua_rawgeti(L, LUA_REGISTRYINDEX, class_http_ref); |
| lua_setmetatable(L, -2); |
| |
| return 1; |
| } |
| |
| /* This function creates and returns an array containing the status-line |
| * elements. This function does not fails. |
| */ |
| __LJMP static int hlua_http_get_stline(lua_State *L, struct htx_sl *sl) |
| { |
| /* Create the table. */ |
| lua_newtable(L); |
| |
| if (sl->flags & HTX_SL_F_IS_RESP) { |
| lua_pushstring(L, "version"); |
| lua_pushlstring(L, HTX_SL_RES_VPTR(sl), HTX_SL_RES_VLEN(sl)); |
| lua_settable(L, -3); |
| lua_pushstring(L, "code"); |
| lua_pushlstring(L, HTX_SL_RES_CPTR(sl), HTX_SL_RES_CLEN(sl)); |
| lua_settable(L, -3); |
| lua_pushstring(L, "reason"); |
| lua_pushlstring(L, HTX_SL_RES_RPTR(sl), HTX_SL_RES_RLEN(sl)); |
| lua_settable(L, -3); |
| } |
| else { |
| lua_pushstring(L, "method"); |
| lua_pushlstring(L, HTX_SL_REQ_MPTR(sl), HTX_SL_REQ_MLEN(sl)); |
| lua_settable(L, -3); |
| lua_pushstring(L, "uri"); |
| lua_pushlstring(L, HTX_SL_REQ_UPTR(sl), HTX_SL_REQ_ULEN(sl)); |
| lua_settable(L, -3); |
| lua_pushstring(L, "version"); |
| lua_pushlstring(L, HTX_SL_REQ_VPTR(sl), HTX_SL_REQ_VLEN(sl)); |
| lua_settable(L, -3); |
| } |
| return 1; |
| } |
| |
| /* This function creates ans returns an array of HTTP headers. |
| * This function does not fails. It is used as wrapper with the |
| * 2 following functions. |
| */ |
| __LJMP static int hlua_http_get_headers(lua_State *L, struct http_msg *msg) |
| { |
| struct htx *htx; |
| int32_t pos; |
| |
| /* Create the table. */ |
| lua_newtable(L); |
| |
| |
| htx = htxbuf(&msg->chn->buf); |
| for (pos = htx_get_first(htx); pos != -1; pos = htx_get_next(htx, pos)) { |
| struct htx_blk *blk = htx_get_blk(htx, pos); |
| enum htx_blk_type type = htx_get_blk_type(blk); |
| struct ist n, v; |
| int len; |
| |
| if (type == HTX_BLK_HDR) { |
| n = htx_get_blk_name(htx,blk); |
| v = htx_get_blk_value(htx, blk); |
| } |
| else if (type == HTX_BLK_EOH) |
| break; |
| else |
| continue; |
| |
| /* Check for existing entry: |
| * assume that the table is on the top of the stack, and |
| * push the key in the stack, the function lua_gettable() |
| * perform the lookup. |
| */ |
| lua_pushlstring(L, n.ptr, n.len); |
| lua_gettable(L, -2); |
| |
| switch (lua_type(L, -1)) { |
| case LUA_TNIL: |
| /* Table not found, create it. */ |
| lua_pop(L, 1); /* remove the nil value. */ |
| lua_pushlstring(L, n.ptr, n.len); /* push the header name as key. */ |
| lua_newtable(L); /* create and push empty table. */ |
| lua_pushlstring(L, v.ptr, v.len); /* push header value. */ |
| lua_rawseti(L, -2, 0); /* index header value (pop it). */ |
| lua_rawset(L, -3); /* index new table with header name (pop the values). */ |
| break; |
| |
| case LUA_TTABLE: |
| /* Entry found: push the value in the table. */ |
| len = lua_rawlen(L, -1); |
| lua_pushlstring(L, v.ptr, v.len); /* push header value. */ |
| lua_rawseti(L, -2, len+1); /* index header value (pop it). */ |
| lua_pop(L, 1); /* remove the table (it is stored in the main table). */ |
| break; |
| |
| default: |
| /* Other cases are errors. */ |
| hlua_pusherror(L, "internal error during the parsing of headers."); |
| WILL_LJMP(lua_error(L)); |
| } |
| } |
| return 1; |
| } |
| |
| __LJMP static int hlua_http_req_get_headers(lua_State *L) |
| { |
| struct hlua_txn *htxn; |
| |
| MAY_LJMP(check_args(L, 1, "req_get_headers")); |
| htxn = MAY_LJMP(hlua_checkhttp(L, 1)); |
| |
| if (htxn->dir != SMP_OPT_DIR_REQ || !IS_HTX_STRM(htxn->s)) |
| WILL_LJMP(lua_error(L)); |
| |
| return hlua_http_get_headers(L, &htxn->s->txn->req); |
| } |
| |
| __LJMP static int hlua_http_res_get_headers(lua_State *L) |
| { |
| struct hlua_txn *htxn; |
| |
| MAY_LJMP(check_args(L, 1, "res_get_headers")); |
| htxn = MAY_LJMP(hlua_checkhttp(L, 1)); |
| |
| if (htxn->dir != SMP_OPT_DIR_RES || !IS_HTX_STRM(htxn->s)) |
| WILL_LJMP(lua_error(L)); |
| |
| return hlua_http_get_headers(L, &htxn->s->txn->rsp); |
| } |
| |
| /* This function replace full header, or just a value in |
| * the request or in the response. It is a wrapper fir the |
| * 4 following functions. |
| */ |
| __LJMP static inline int hlua_http_rep_hdr(lua_State *L, struct http_msg *msg, int full) |
| { |
| size_t name_len; |
| const char *name = MAY_LJMP(luaL_checklstring(L, 2, &name_len)); |
| const char *reg = MAY_LJMP(luaL_checkstring(L, 3)); |
| const char *value = MAY_LJMP(luaL_checkstring(L, 4)); |
| struct htx *htx; |
| struct my_regex *re; |
| |
| if (!(re = regex_comp(reg, 1, 1, NULL))) |
| WILL_LJMP(luaL_argerror(L, 3, "invalid regex")); |
| |
| htx = htxbuf(&msg->chn->buf); |
| http_replace_hdrs(chn_strm(msg->chn), htx, ist2(name, name_len), value, re, full); |
| regex_free(re); |
| return 0; |
| } |
| |
| __LJMP static int hlua_http_req_rep_hdr(lua_State *L) |
| { |
| struct hlua_txn *htxn; |
| |
| MAY_LJMP(check_args(L, 4, "req_rep_hdr")); |
| htxn = MAY_LJMP(hlua_checkhttp(L, 1)); |
| |
| if (htxn->dir != SMP_OPT_DIR_REQ || !IS_HTX_STRM(htxn->s)) |
| WILL_LJMP(lua_error(L)); |
| |
| return MAY_LJMP(hlua_http_rep_hdr(L, &htxn->s->txn->req, 1)); |
| } |
| |
| __LJMP static int hlua_http_res_rep_hdr(lua_State *L) |
| { |
| struct hlua_txn *htxn; |
| |
| MAY_LJMP(check_args(L, 4, "res_rep_hdr")); |
| htxn = MAY_LJMP(hlua_checkhttp(L, 1)); |
| |
| if (htxn->dir != SMP_OPT_DIR_RES || !IS_HTX_STRM(htxn->s)) |
| WILL_LJMP(lua_error(L)); |
| |
| return MAY_LJMP(hlua_http_rep_hdr(L, &htxn->s->txn->rsp, 1)); |
| } |
| |
| __LJMP static int hlua_http_req_rep_val(lua_State *L) |
| { |
| struct hlua_txn *htxn; |
| |
| MAY_LJMP(check_args(L, 4, "req_rep_hdr")); |
| htxn = MAY_LJMP(hlua_checkhttp(L, 1)); |
| |
| if (htxn->dir != SMP_OPT_DIR_REQ || !IS_HTX_STRM(htxn->s)) |
| WILL_LJMP(lua_error(L)); |
| |
| return MAY_LJMP(hlua_http_rep_hdr(L, &htxn->s->txn->req, 0)); |
| } |
| |
| __LJMP static int hlua_http_res_rep_val(lua_State *L) |
| { |
| struct hlua_txn *htxn; |
| |
| MAY_LJMP(check_args(L, 4, "res_rep_val")); |
| htxn = MAY_LJMP(hlua_checkhttp(L, 1)); |
| |
| if (htxn->dir != SMP_OPT_DIR_RES || !IS_HTX_STRM(htxn->s)) |
| WILL_LJMP(lua_error(L)); |
| |
| return MAY_LJMP(hlua_http_rep_hdr(L, &htxn->s->txn->rsp, 0)); |
| } |
| |
| /* This function deletes all the occurrences of an header. |
| * It is a wrapper for the 2 following functions. |
| */ |
| __LJMP static inline int hlua_http_del_hdr(lua_State *L, struct http_msg *msg) |
| { |
| size_t len; |
| const char *name = MAY_LJMP(luaL_checklstring(L, 2, &len)); |
| struct htx *htx = htxbuf(&msg->chn->buf); |
| struct http_hdr_ctx ctx; |
| |
| ctx.blk = NULL; |
| while (http_find_header(htx, ist2(name, len), &ctx, 1)) |
| http_remove_header(htx, &ctx); |
| return 0; |
| } |
| |
| __LJMP static int hlua_http_req_del_hdr(lua_State *L) |
| { |
| struct hlua_txn *htxn; |
| |
| MAY_LJMP(check_args(L, 2, "req_del_hdr")); |
| htxn = MAY_LJMP(hlua_checkhttp(L, 1)); |
| |
| if (htxn->dir != SMP_OPT_DIR_REQ || !IS_HTX_STRM(htxn->s)) |
| WILL_LJMP(lua_error(L)); |
| |
| return hlua_http_del_hdr(L, &htxn->s->txn->req); |
| } |
| |
| __LJMP static int hlua_http_res_del_hdr(lua_State *L) |
| { |
| struct hlua_txn *htxn; |
| |
| MAY_LJMP(check_args(L, 2, "res_del_hdr")); |
| htxn = MAY_LJMP(hlua_checkhttp(L, 1)); |
| |
| if (htxn->dir != SMP_OPT_DIR_RES || !IS_HTX_STRM(htxn->s)) |
| WILL_LJMP(lua_error(L)); |
| |
| return hlua_http_del_hdr(L, &htxn->s->txn->rsp); |
| } |
| |
| /* This function adds an header. It is a wrapper used by |
| * the 2 following functions. |
| */ |
| __LJMP static inline int hlua_http_add_hdr(lua_State *L, struct http_msg *msg) |
| { |
| size_t name_len; |
| const char *name = MAY_LJMP(luaL_checklstring(L, 2, &name_len)); |
| size_t value_len; |
| const char *value = MAY_LJMP(luaL_checklstring(L, 3, &value_len)); |
| struct htx *htx = htxbuf(&msg->chn->buf); |
| |
| lua_pushboolean(L, http_add_header(htx, ist2(name, name_len), |
| ist2(value, value_len))); |
| return 0; |
| } |
| |
| __LJMP static int hlua_http_req_add_hdr(lua_State *L) |
| { |
| struct hlua_txn *htxn; |
| |
| MAY_LJMP(check_args(L, 3, "req_add_hdr")); |
| htxn = MAY_LJMP(hlua_checkhttp(L, 1)); |
| |
| if (htxn->dir != SMP_OPT_DIR_REQ || !IS_HTX_STRM(htxn->s)) |
| WILL_LJMP(lua_error(L)); |
| |
| return hlua_http_add_hdr(L, &htxn->s->txn->req); |
| } |
| |
| __LJMP static int hlua_http_res_add_hdr(lua_State *L) |
| { |
| struct hlua_txn *htxn; |
| |
| MAY_LJMP(check_args(L, 3, "res_add_hdr")); |
| htxn = MAY_LJMP(hlua_checkhttp(L, 1)); |
| |
| if (htxn->dir != SMP_OPT_DIR_RES || !IS_HTX_STRM(htxn->s)) |
| WILL_LJMP(lua_error(L)); |
| |
| return hlua_http_add_hdr(L, &htxn->s->txn->rsp); |
| } |
| |
| static int hlua_http_req_set_hdr(lua_State *L) |
| { |
| struct hlua_txn *htxn; |
| |
| MAY_LJMP(check_args(L, 3, "req_set_hdr")); |
| htxn = MAY_LJMP(hlua_checkhttp(L, 1)); |
| |
| if (htxn->dir != SMP_OPT_DIR_REQ || !IS_HTX_STRM(htxn->s)) |
| WILL_LJMP(lua_error(L)); |
| |
| hlua_http_del_hdr(L, &htxn->s->txn->req); |
| return hlua_http_add_hdr(L, &htxn->s->txn->req); |
| } |
| |
| static int hlua_http_res_set_hdr(lua_State *L) |
| { |
| struct hlua_txn *htxn; |
| |
| MAY_LJMP(check_args(L, 3, "res_set_hdr")); |
| htxn = MAY_LJMP(hlua_checkhttp(L, 1)); |
| |
| if (htxn->dir != SMP_OPT_DIR_RES || !IS_HTX_STRM(htxn->s)) |
| WILL_LJMP(lua_error(L)); |
| |
| hlua_http_del_hdr(L, &htxn->s->txn->rsp); |
| return hlua_http_add_hdr(L, &htxn->s->txn->rsp); |
| } |
| |
| /* This function set the method. */ |
| static int hlua_http_req_set_meth(lua_State *L) |
| { |
| struct hlua_txn *htxn = MAY_LJMP(hlua_checkhttp(L, 1)); |
| size_t name_len; |
| const char *name = MAY_LJMP(luaL_checklstring(L, 2, &name_len)); |
| |
| if (htxn->dir != SMP_OPT_DIR_REQ || !IS_HTX_STRM(htxn->s)) |
| WILL_LJMP(lua_error(L)); |
| |
| lua_pushboolean(L, http_req_replace_stline(0, name, name_len, htxn->p, htxn->s) != -1); |
| return 1; |
| } |
| |
| /* This function set the method. */ |
| static int hlua_http_req_set_path(lua_State *L) |
| { |
| struct hlua_txn *htxn = MAY_LJMP(hlua_checkhttp(L, 1)); |
| size_t name_len; |
| const char *name = MAY_LJMP(luaL_checklstring(L, 2, &name_len)); |
| |
| if (htxn->dir != SMP_OPT_DIR_REQ || !IS_HTX_STRM(htxn->s)) |
| WILL_LJMP(lua_error(L)); |
| |
| lua_pushboolean(L, http_req_replace_stline(1, name, name_len, htxn->p, htxn->s) != -1); |
| return 1; |
| } |
| |
| /* This function set the query-string. */ |
| static int hlua_http_req_set_query(lua_State *L) |
| { |
| struct hlua_txn *htxn = MAY_LJMP(hlua_checkhttp(L, 1)); |
| size_t name_len; |
| const char *name = MAY_LJMP(luaL_checklstring(L, 2, &name_len)); |
| |
| if (htxn->dir != SMP_OPT_DIR_REQ || !IS_HTX_STRM(htxn->s)) |
| WILL_LJMP(lua_error(L)); |
| |
| /* Check length. */ |
| if (name_len > trash.size - 1) { |
| lua_pushboolean(L, 0); |
| return 1; |
| } |
| |
| /* Add the mark question as prefix. */ |
| chunk_reset(&trash); |
| trash.area[trash.data++] = '?'; |
| memcpy(trash.area + trash.data, name, name_len); |
| trash.data += name_len; |
| |
| lua_pushboolean(L, |
| http_req_replace_stline(2, trash.area, trash.data, htxn->p, htxn->s) != -1); |
| return 1; |
| } |
| |
| /* This function set the uri. */ |
| static int hlua_http_req_set_uri(lua_State *L) |
| { |
| struct hlua_txn *htxn = MAY_LJMP(hlua_checkhttp(L, 1)); |
| size_t name_len; |
| const char *name = MAY_LJMP(luaL_checklstring(L, 2, &name_len)); |
| |
| if (htxn->dir != SMP_OPT_DIR_REQ || !IS_HTX_STRM(htxn->s)) |
| WILL_LJMP(lua_error(L)); |
| |
| lua_pushboolean(L, http_req_replace_stline(3, name, name_len, htxn->p, htxn->s) != -1); |
| return 1; |
| } |
| |
| /* This function set the response code & optionally reason. */ |
| static int hlua_http_res_set_status(lua_State *L) |
| { |
| struct hlua_txn *htxn = MAY_LJMP(hlua_checkhttp(L, 1)); |
| unsigned int code = MAY_LJMP(luaL_checkinteger(L, 2)); |
| const char *str = MAY_LJMP(luaL_optlstring(L, 3, NULL, NULL)); |
| const struct ist reason = ist2(str, (str ? strlen(str) : 0)); |
| |
| if (htxn->dir != SMP_OPT_DIR_RES || !IS_HTX_STRM(htxn->s)) |
| WILL_LJMP(lua_error(L)); |
| |
| http_res_set_status(code, reason, htxn->s); |
| return 0; |
| } |
| |
| /* |
| * |
| * |
| * Class HTTPMessage |
| * |
| * |
| */ |
| |
| /* Returns a struct http_msg if the stack entry "ud" is a class HTTPMessage, |
| * otherwise it throws an error. |
| */ |
| __LJMP static struct http_msg *hlua_checkhttpmsg(lua_State *L, int ud) |
| { |
| return MAY_LJMP(hlua_checkudata(L, ud, class_http_msg_ref)); |
| } |
| |
| /* Creates and pushes on the stack a HTTP object according with a current TXN. |
| */ |
| static int hlua_http_msg_new(lua_State *L, struct http_msg *msg) |
| { |
| /* Check stack size. */ |
| if (!lua_checkstack(L, 3)) |
| return 0; |
| |
| lua_newtable(L); |
| lua_pushlightuserdata(L, msg); |
| lua_rawseti(L, -2, 0); |
| |
| /* Create the "channel" field that contains the request channel object. */ |
| lua_pushstring(L, "channel"); |
| if (!hlua_channel_new(L, msg->chn)) |
| return 0; |
| lua_rawset(L, -3); |
| |
| /* Pop a class stream metatable and affect it to the table. */ |
| lua_rawgeti(L, LUA_REGISTRYINDEX, class_http_msg_ref); |
| lua_setmetatable(L, -2); |
| |
| return 1; |
| } |
| |
| /* Helper function returning a filter attached to the HTTP message at the |
| * position <ud> in the stack, filling the current offset and length of the |
| * filter. If no filter is attached, NULL is returned and <offset> and <len> are |
| * filled with output and input length respectively. |
| */ |
| static struct filter *hlua_http_msg_filter(lua_State *L, int ud, struct http_msg *msg, size_t *offset, size_t *len) |
| { |
| struct channel *chn = msg->chn; |
| struct htx *htx = htxbuf(&chn->buf); |
| struct filter *filter = NULL; |
| |
| *offset = co_data(msg->chn); |
| *len = htx->data - co_data(msg->chn); |
| |
| if (lua_getfield(L, ud, "__filter") == LUA_TLIGHTUSERDATA) { |
| filter = lua_touserdata (L, -1); |
| if (msg->msg_state >= HTTP_MSG_DATA) { |
| struct hlua_flt_ctx *flt_ctx = filter->ctx; |
| |
| *offset = flt_ctx->cur_off[CHN_IDX(chn)]; |
| *len = flt_ctx->cur_len[CHN_IDX(chn)]; |
| } |
| } |
| |
| lua_pop(L, 1); |
| return filter; |
| } |
| |
| /* Returns true if the channel attached to the HTTP message is the response |
| * channel. |
| */ |
| __LJMP static int hlua_http_msg_is_resp(lua_State *L) |
| { |
| struct http_msg *msg; |
| |
| MAY_LJMP(check_args(L, 1, "is_resp")); |
| msg = MAY_LJMP(hlua_checkhttpmsg(L, 1)); |
| |
| lua_pushboolean(L, !!(msg->chn->flags & CF_ISRESP)); |
| return 1; |
| } |
| |
| /* Returns an array containing the elements status-line of the HTTP message. It relies |
| * on hlua_http_get_stline(). |
| */ |
| __LJMP static int hlua_http_msg_get_stline(lua_State *L) |
| { |
| struct http_msg *msg; |
| struct htx *htx; |
| struct htx_sl *sl; |
| |
| MAY_LJMP(check_args(L, 1, "get_stline")); |
| msg = MAY_LJMP(hlua_checkhttpmsg(L, 1)); |
| |
| if (msg->msg_state > HTTP_MSG_BODY) |
| WILL_LJMP(lua_error(L)); |
| |
| htx = htxbuf(&msg->chn->buf); |
| sl = http_get_stline(htx); |
| if (!sl) |
| return 0; |
| return hlua_http_get_stline(L, sl); |
| } |
| |
| /* Returns an array containing all headers of the HTTP message. it relies on |
| * hlua_http_get_headers(). |
| */ |
| __LJMP static int hlua_http_msg_get_headers(lua_State *L) |
| { |
| struct http_msg *msg; |
| |
| MAY_LJMP(check_args(L, 1, "get_headers")); |
| msg = MAY_LJMP(hlua_checkhttpmsg(L, 1)); |
| |
| if (msg->msg_state > HTTP_MSG_BODY) |
| WILL_LJMP(lua_error(L)); |
| |
| return hlua_http_get_headers(L, msg); |
| } |
| |
| /* Deletes all occurrences of an header in the HTTP message matching on its |
| * name. It relies on hlua_http_del_hdr(). |
| */ |
| __LJMP static int hlua_http_msg_del_hdr(lua_State *L) |
| { |
| struct http_msg *msg; |
| |
| MAY_LJMP(check_args(L, 2, "del_header")); |
| msg = MAY_LJMP(hlua_checkhttpmsg(L, 1)); |
| |
| if (msg->msg_state > HTTP_MSG_BODY) |
| WILL_LJMP(lua_error(L)); |
| |
| return hlua_http_del_hdr(L, msg); |
| } |
| |
| /* Matches the full value line of all occurrences of an header in the HTTP |
| * message given its name against a regex and replaces it if it matches. It |
| * relies on hlua_http_rep_hdr(). |
| */ |
| __LJMP static int hlua_http_msg_rep_hdr(lua_State *L) |
| { |
| struct http_msg *msg; |
| |
| MAY_LJMP(check_args(L, 4, "rep_header")); |
| msg = MAY_LJMP(hlua_checkhttpmsg(L, 1)); |
| |
| if (msg->msg_state > HTTP_MSG_BODY) |
| WILL_LJMP(lua_error(L)); |
| |
| return hlua_http_rep_hdr(L, msg, 1); |
| } |
| |
| /* Matches all comma-separated values of all occurrences of an header in the HTTP |
| * message given its name against a regex and replaces it if it matches. It |
| * relies on hlua_http_rep_hdr(). |
| */ |
| __LJMP static int hlua_http_msg_rep_val(lua_State *L) |
| { |
| struct http_msg *msg; |
| |
| MAY_LJMP(check_args(L, 4, "rep_value")); |
| msg = MAY_LJMP(hlua_checkhttpmsg(L, 1)); |
| |
| if (msg->msg_state > HTTP_MSG_BODY) |
| WILL_LJMP(lua_error(L)); |
| |
| return hlua_http_rep_hdr(L, msg, 0); |
| } |
| |
| /* Add an header in the HTTP message. It relies on hlua_http_add_hdr() */ |
| __LJMP static int hlua_http_msg_add_hdr(lua_State *L) |
| { |
| struct http_msg *msg; |
| |
| MAY_LJMP(check_args(L, 3, "add_header")); |
| msg = MAY_LJMP(hlua_checkhttpmsg(L, 1)); |
| |
| if (msg->msg_state > HTTP_MSG_BODY) |
| WILL_LJMP(lua_error(L)); |
| |
| return hlua_http_add_hdr(L, msg); |
| } |
| |
| /* Add an header in the HTTP message removing existing headers with the same |
| * name. It relies on hlua_http_del_hdr() and hlua_http_add_hdr(). |
| */ |
| __LJMP static int hlua_http_msg_set_hdr(lua_State *L) |
| { |
| struct http_msg *msg; |
| |
| MAY_LJMP(check_args(L, 3, "set_header")); |
| msg = MAY_LJMP(hlua_checkhttpmsg(L, 1)); |
| |
| if (msg->msg_state > HTTP_MSG_BODY) |
| WILL_LJMP(lua_error(L)); |
| |
| hlua_http_del_hdr(L, msg); |
| return hlua_http_add_hdr(L, msg); |
| } |
| |
| /* Rewrites the request method. It relies on http_req_replace_stline(). */ |
| __LJMP static int hlua_http_msg_set_meth(lua_State *L) |
| { |
| struct stream *s; |
| struct http_msg *msg; |
| const char *name; |
| size_t name_len; |
| |
| MAY_LJMP(check_args(L, 2, "set_method")); |
| msg = MAY_LJMP(hlua_checkhttpmsg(L, 1)); |
| name = MAY_LJMP(luaL_checklstring(L, 2, &name_len)); |
| |
| if ((msg->chn->flags & CF_ISRESP) || msg->msg_state > HTTP_MSG_BODY) |
| WILL_LJMP(lua_error(L)); |
| |
| s = chn_strm(msg->chn); |
| lua_pushboolean(L, http_req_replace_stline(0, name, name_len, s->be, s) != -1); |
| return 1; |
| } |
| |
| /* Rewrites the request path. It relies on http_req_replace_stline(). */ |
| __LJMP static int hlua_http_msg_set_path(lua_State *L) |
| { |
| struct stream *s; |
| struct http_msg *msg; |
| const char *name; |
| size_t name_len; |
| |
| MAY_LJMP(check_args(L, 2, "set_path")); |
| msg = MAY_LJMP(hlua_checkhttpmsg(L, 1)); |
| name = MAY_LJMP(luaL_checklstring(L, 2, &name_len)); |
| |
| if ((msg->chn->flags & CF_ISRESP) || msg->msg_state > HTTP_MSG_BODY) |
| WILL_LJMP(lua_error(L)); |
| |
| s = chn_strm(msg->chn); |
| lua_pushboolean(L, http_req_replace_stline(1, name, name_len, s->be, s) != -1); |
| return 1; |
| } |
| |
| /* Rewrites the request query-string. It relies on http_req_replace_stline(). */ |
| __LJMP static int hlua_http_msg_set_query(lua_State *L) |
| { |
| struct stream *s; |
| struct http_msg *msg; |
| const char *name; |
| size_t name_len; |
| |
| MAY_LJMP(check_args(L, 2, "set_query")); |
| msg = MAY_LJMP(hlua_checkhttpmsg(L, 1)); |
| name = MAY_LJMP(luaL_checklstring(L, 2, &name_len)); |
| |
| if ((msg->chn->flags & CF_ISRESP) || msg->msg_state > HTTP_MSG_BODY) |
| WILL_LJMP(lua_error(L)); |
| |
| /* Check length. */ |
| if (name_len > trash.size - 1) { |
| lua_pushboolean(L, 0); |
| return 1; |
| } |
| |
| /* Add the mark question as prefix. */ |
| chunk_reset(&trash); |
| trash.area[trash.data++] = '?'; |
| memcpy(trash.area + trash.data, name, name_len); |
| trash.data += name_len; |
| |
| s = chn_strm(msg->chn); |
| lua_pushboolean(L, http_req_replace_stline(2, trash.area, trash.data, s->be, s) != -1); |
| return 1; |
| } |
| |
| /* Rewrites the request URI. It relies on http_req_replace_stline(). */ |
| __LJMP static int hlua_http_msg_set_uri(lua_State *L) |
| { |
| struct stream *s; |
| struct http_msg *msg; |
| const char *name; |
| size_t name_len; |
| |
| MAY_LJMP(check_args(L, 2, "set_uri")); |
| msg = MAY_LJMP(hlua_checkhttpmsg(L, 1)); |
| name = MAY_LJMP(luaL_checklstring(L, 2, &name_len)); |
| |
| if ((msg->chn->flags & CF_ISRESP) || msg->msg_state > HTTP_MSG_BODY) |
| WILL_LJMP(lua_error(L)); |
| |
| s = chn_strm(msg->chn); |
| lua_pushboolean(L, http_req_replace_stline(3, name, name_len, s->be, s) != -1); |
| return 1; |
| } |
| |
| /* Rewrites the response status code. It relies on http_res_set_status(). */ |
| __LJMP static int hlua_http_msg_set_status(lua_State *L) |
| { |
| struct http_msg *msg; |
| unsigned int code; |
| const char *reason; |
| size_t reason_len; |
| |
| msg = MAY_LJMP(hlua_checkhttpmsg(L, 1)); |
| code = MAY_LJMP(luaL_checkinteger(L, 2)); |
| reason = MAY_LJMP(luaL_optlstring(L, 3, NULL, &reason_len)); |
| |
| if (!(msg->chn->flags & CF_ISRESP) || msg->msg_state > HTTP_MSG_BODY) |
| WILL_LJMP(lua_error(L)); |
| |
| lua_pushboolean(L, http_res_set_status(code, ist2(reason, reason_len), chn_strm(msg->chn)) != -1); |
| return 1; |
| } |
| |
| /* Returns true if the HTTP message is full. */ |
| __LJMP static int hlua_http_msg_is_full(lua_State *L) |
| { |
| struct http_msg *msg; |
| |
| MAY_LJMP(check_args(L, 1, "is_full")); |
| msg = MAY_LJMP(hlua_checkhttpmsg(L, 1)); |
| lua_pushboolean(L, channel_full(msg->chn, 0)); |
| return 1; |
| } |
| |
| /* Returns true if the HTTP message may still receive data. */ |
| __LJMP static int hlua_http_msg_may_recv(lua_State *L) |
| { |
| struct http_msg *msg; |
| struct htx *htx; |
| |
| MAY_LJMP(check_args(L, 1, "may_recv")); |
| msg = MAY_LJMP(hlua_checkhttpmsg(L, 1)); |
| htx = htxbuf(&msg->chn->buf); |
| lua_pushboolean(L, (htx_expect_more(htx) && !channel_input_closed(msg->chn) && channel_may_recv(msg->chn))); |
| return 1; |
| } |
| |
| /* Returns true if the HTTP message EOM was received */ |
| __LJMP static int hlua_http_msg_is_eom(lua_State *L) |
| { |
| struct http_msg *msg; |
| struct htx *htx; |
| |
| MAY_LJMP(check_args(L, 1, "may_recv")); |
| msg = MAY_LJMP(hlua_checkhttpmsg(L, 1)); |
| htx = htxbuf(&msg->chn->buf); |
| lua_pushboolean(L, !htx_expect_more(htx)); |
| return 1; |
| } |
| |
| /* Returns the number of bytes available in the input side of the HTTP |
| * message. This function never fails. |
| */ |
| __LJMP static int hlua_http_msg_get_in_len(lua_State *L) |
| { |
| struct http_msg *msg; |
| size_t output, input; |
| |
| MAY_LJMP(check_args(L, 1, "input")); |
| msg = MAY_LJMP(hlua_checkhttpmsg(L, 1)); |
| hlua_http_msg_filter(L, 1, msg, &output, &input); |
| lua_pushinteger(L, input); |
| return 1; |
| } |
| |
| /* Returns the number of bytes available in the output side of the HTTP |
| * message. This function never fails. |
| */ |
| __LJMP static int hlua_http_msg_get_out_len(lua_State *L) |
| { |
| struct http_msg *msg; |
| size_t output, input; |
| |
| MAY_LJMP(check_args(L, 1, "output")); |
| msg = MAY_LJMP(hlua_checkhttpmsg(L, 1)); |
| hlua_http_msg_filter(L, 1, msg, &output, &input); |
| lua_pushinteger(L, output); |
| return 1; |
| } |
| |
| /* Copies at most <len> bytes of DATA blocks from the HTTP message <msg> |
| * starting at the offset <offset> and put it in a string LUA variables. It |
| * returns the built string length. It stops on the first non-DATA HTX |
| * block. This function is called during the payload filtering, so the headers |
| * are already scheduled for output (from the filter point of view). |
| */ |
| static int _hlua_http_msg_dup(struct http_msg *msg, lua_State *L, size_t offset, size_t len) |
| { |
| struct htx *htx = htxbuf(&msg->chn->buf); |
| struct htx_blk *blk; |
| struct htx_ret htxret; |
| luaL_Buffer b; |
| int ret = 0; |
| |
| luaL_buffinit(L, &b); |
| htxret = htx_find_offset(htx, offset); |
| for (blk = htxret.blk, offset = htxret.ret; blk && len; blk = htx_get_next_blk(htx, blk)) { |
| enum htx_blk_type type = htx_get_blk_type(blk); |
| struct ist v; |
| |
| switch (type) { |
| case HTX_BLK_UNUSED: |
| break; |
| |
| case HTX_BLK_DATA: |
| v = htx_get_blk_value(htx, blk); |
| v = istadv(v, offset); |
| v = isttrim(v, len); |
| |
| luaL_addlstring(&b, v.ptr, v.len); |
| ret += v.len; |
| break; |
| |
| default: |
| if (!ret) |
| goto no_data; |
| goto end; |
| } |
| offset = 0; |
| } |
| |
| end: |
| if (!ret && (htx->flags & HTX_FL_EOM)) |
| goto no_data; |
| luaL_pushresult(&b); |
| return ret; |
| |
| no_data: |
| /* Remove the empty string and push nil on the stack */ |
| lua_pop(L, 1); |
| lua_pushnil(L); |
| return 0; |
| } |
| |
| /* Copies the string <str> to the HTTP message <msg> at the offset |
| * <offset>. This function returns -1 if data cannot be copied. Otherwise, it |
| * returns the amount of data written. This function is responsible to update |
| * the filter context. |
| */ |
| static int _hlua_http_msg_insert(struct http_msg *msg, struct filter *filter, struct ist str, size_t offset) |
| { |
| struct htx *htx = htx_from_buf(&msg->chn->buf); |
| struct htx_ret htxret; |
| int /*max, */ret = 0; |
| |
| /* Nothing to do, just return */ |
| if (unlikely(istlen(str) == 0)) |
| goto end; |
| |
| if (istlen(str) > htx_free_data_space(htx)) { |
| ret = -1; |
| goto end; |
| } |
| |
| htxret = htx_find_offset(htx, offset); |
| if (!htxret.blk || htx_get_blk_type(htxret.blk) != HTX_BLK_DATA) { |
| if (!htx_add_last_data(htx, str)) |
| goto end; |
| } |
| else { |
| struct ist v = htx_get_blk_value(htx, htxret.blk); |
| v.ptr += htxret.ret; |
| v.len = 0; |
| if (!htx_replace_blk_value(htx, htxret.blk, v, str)) |
| goto end; |
| } |
| ret = str.len; |
| if (ret) { |
| struct hlua_flt_ctx *flt_ctx = filter->ctx; |
| flt_update_offsets(filter, msg->chn, ret); |
| flt_ctx->cur_len[CHN_IDX(msg->chn)] += ret; |
| } |
| |
| end: |
| htx_to_buf(htx, &msg->chn->buf); |
| return ret; |
| } |
| |
| /* Helper function removing at most <len> bytes of DATA blocks at the absolute |
| * position <offset>. It stops on the first non-DATA HTX block. This function is |
| * called during the payload filtering, so the headers are already scheduled for |
| * output (from the filter point of view). This function is responsible to |
| * update the filter context. |
| */ |
| static void _hlua_http_msg_delete(struct http_msg *msg, struct filter *filter, size_t offset, size_t len) |
| { |
| struct hlua_flt_ctx *flt_ctx = filter->ctx; |
| struct htx *htx = htx_from_buf(&msg->chn->buf); |
| struct htx_blk *blk; |
| struct htx_ret htxret; |
| size_t ret = 0; |
| |
| /* Be sure <len> is always the amount of DATA to remove */ |
| if (htx->data == offset+len && htx_get_tail_type(htx) == HTX_BLK_DATA) { |
| /* When htx tail type == HTX_BLK_DATA, no need to take care |
| * of special blocks like HTX_BLK_EOT. |
| * We simply truncate after offset |
| * (truncate targeted blk and discard the following ones) |
| */ |
| htx_truncate(htx, offset); |
| ret = len; |
| goto end; |
| } |
| |
| htxret = htx_find_offset(htx, offset); |
| blk = htxret.blk; |
| if (htxret.ret) { |
| /* dealing with offset: we need to trim targeted blk */ |
| struct ist v; |
| |
| if (htx_get_blk_type(blk) != HTX_BLK_DATA) |
| goto end; |
| |
| v = htx_get_blk_value(htx, blk); |
| v = istadv(v, htxret.ret); |
| |
| v = isttrim(v, len); |
| /* trimming data in blk: discard everything after the offset |
| * (replace 'v' with 'IST_NULL') |
| */ |
| blk = htx_replace_blk_value(htx, blk, v, IST_NULL); |
| if (blk && v.len < len) { |
| /* In this case, caller wants to keep removing data, |
| * but we need to spare current blk |
| * because it was already trimmed |
| */ |
| blk = htx_get_next_blk(htx, blk); |
| } |
| len -= v.len; |
| ret += v.len; |
| } |
| |
| |
| while (blk && len) { |
| /* there is more data that needs to be discarded */ |
| enum htx_blk_type type = htx_get_blk_type(blk); |
| uint32_t sz = htx_get_blksz(blk); |
| |
| switch (type) { |
| case HTX_BLK_UNUSED: |
| break; |
| |
| case HTX_BLK_DATA: |
| if (len < sz) { |
| /* don't discard whole blk, only part of it |
| * (from the beginning) |
| */ |
| htx_cut_data_blk(htx, blk, len); |
| ret += len; |
| goto end; |
| } |
| break; |
| |
| default: |
| /* HTX_BLK_EOT blk won't be removed */ |
| goto end; |
| } |
| |
| /* Remove all the data block */ |
| len -= sz; |
| ret += sz; |
| blk = htx_remove_blk(htx, blk); |
| } |
| |
| end: |
| flt_update_offsets(filter, msg->chn, -ret); |
| flt_ctx->cur_len[CHN_IDX(msg->chn)] -= ret; |
| /* WARNING: we don't call htx_to_buf() on purpose, because we don't want |
| * to loose the EOM flag if the message is empty. |
| */ |
| } |
| |
| /* Copies input data found in an HTTP message. Unlike the channel function used |
| * to duplicate raw data, this one can only be called inside a filter, from |
| * http_payload callback. So it cannot yield. An exception is returned if it is |
| * called from another callback. If nothing was copied, a nil value is pushed on |
| * the stack. |
| */ |
| __LJMP static int hlua_http_msg_get_body(lua_State *L) |
| { |
| struct http_msg *msg; |
| struct filter *filter; |
| size_t output, input; |
| int offset, len; |
| |
| if (lua_gettop(L) < 1 || lua_gettop(L) > 3) |
| WILL_LJMP(luaL_error(L, "'data' expects at most 2 arguments")); |
| msg = MAY_LJMP(hlua_checkhttpmsg(L, 1)); |
| |
| if (msg->msg_state < HTTP_MSG_DATA) |
| WILL_LJMP(lua_error(L)); |
| |
| filter = hlua_http_msg_filter(L, 1, msg, &output, &input); |
| if (!filter || !hlua_filter_from_payload(filter)) |
| WILL_LJMP(lua_error(L)); |
| |
| if (!ci_data(msg->chn) && channel_input_closed(msg->chn)) { |
| lua_pushnil(L); |
| return 1; |
| } |
| |
| offset = output; |
| if (lua_gettop(L) > 1) { |
| offset = MAY_LJMP(luaL_checkinteger(L, 2)); |
| if (offset < 0) |
| offset = MAX(0, (int)input + offset); |
| offset += output; |
| if (offset < output || offset > input + output) { |
| lua_pushfstring(L, "offset out of range."); |
| WILL_LJMP(lua_error(L)); |
| } |
| } |
| len = output + input - offset; |
| if (lua_gettop(L) == 3) { |
| len = MAY_LJMP(luaL_checkinteger(L, 3)); |
| if (!len) |
| goto dup; |
| if (len == -1) |
| len = global.tune.bufsize; |
| if (len < 0) { |
| lua_pushfstring(L, "length out of range."); |
| WILL_LJMP(lua_error(L)); |
| } |
| } |
| |
| dup: |
| _hlua_http_msg_dup(msg, L, offset, len); |
| return 1; |
| } |
| |
| /* Appends a string to the HTTP message, after all existing DATA blocks but |
| * before the trailers, if any. It returns the amount of data written or -1 if |
| * nothing was copied. Unlike the channel function used to append data, this one |
| * can only be called inside a filter, from http_payload callback. So it cannot |
| * yield. An exception is returned if it is called from another callback. |
| */ |
| __LJMP static int hlua_http_msg_append(lua_State *L) |
| { |
| struct http_msg *msg; |
| struct filter *filter; |
| const char *str; |
| size_t offset, len, sz; |
| int ret; |
| |
| MAY_LJMP(check_args(L, 2, "append")); |
| msg = MAY_LJMP(hlua_checkhttpmsg(L, 1)); |
| |
| if (msg->msg_state < HTTP_MSG_DATA) |
| WILL_LJMP(lua_error(L)); |
| |
| str = MAY_LJMP(luaL_checklstring(L, 2, &sz)); |
| filter = hlua_http_msg_filter(L, 1, msg, &offset, &len); |
| if (!filter || !hlua_filter_from_payload(filter)) |
| WILL_LJMP(lua_error(L)); |
| |
| ret = _hlua_http_msg_insert(msg, filter, ist2(str, sz), offset+len); |
| lua_pushinteger(L, ret); |
| return 1; |
| } |
| |
| /* Prepends a string to the HTTP message, before all existing DATA blocks. It |
| * returns the amount of data written or -1 if nothing was copied. Unlike the |
| * channel function used to prepend data, this one can only be called inside a |
| * filter, from http_payload callback. So it cannot yield. An exception is |
| * returned if it is called from another callback. |
| */ |
| __LJMP static int hlua_http_msg_prepend(lua_State *L) |
| { |
| struct http_msg *msg; |
| struct filter *filter; |
| const char *str; |
| size_t offset, len, sz; |
| int ret; |
| |
| MAY_LJMP(check_args(L, 2, "prepend")); |
| msg = MAY_LJMP(hlua_checkhttpmsg(L, 1)); |
| |
| if (msg->msg_state < HTTP_MSG_DATA) |
| WILL_LJMP(lua_error(L)); |
| |
| str = MAY_LJMP(luaL_checklstring(L, 2, &sz)); |
| filter = hlua_http_msg_filter(L, 1, msg, &offset, &len); |
| if (!filter || !hlua_filter_from_payload(filter)) |
| WILL_LJMP(lua_error(L)); |
| |
| ret = _hlua_http_msg_insert(msg, filter, ist2(str, sz), offset); |
| lua_pushinteger(L, ret); |
| return 1; |
| } |
| |
| /* Inserts a string to the HTTP message at a given offset. By default the string |
| * is appended at the end of DATA blocks. It returns the amount of data written |
| * or -1 if nothing was copied. Unlike the channel function used to insert data, |
| * this one can only be called inside a filter, from http_payload callback. So |
| * it cannot yield. An exception is returned if it is called from another |
| * callback. |
| */ |
| __LJMP static int hlua_http_msg_insert_data(lua_State *L) |
| { |
| struct http_msg *msg; |
| struct filter *filter; |
| const char *str; |
| size_t input, output, sz; |
| int offset; |
| int ret; |
| |
| if (lua_gettop(L) < 2 || lua_gettop(L) > 3) |
| WILL_LJMP(luaL_error(L, "'insert' expects at least 1 argument and at most 2 arguments")); |
| msg = MAY_LJMP(hlua_checkhttpmsg(L, 1)); |
| |
| if (msg->msg_state < HTTP_MSG_DATA) |
| WILL_LJMP(lua_error(L)); |
| |
| str = MAY_LJMP(luaL_checklstring(L, 2, &sz)); |
| filter = hlua_http_msg_filter(L, 1, msg, &output, &input); |
| if (!filter || !hlua_filter_from_payload(filter)) |
| WILL_LJMP(lua_error(L)); |
| |
| offset = output; |
| if (lua_gettop(L) > 2) { |
| offset = MAY_LJMP(luaL_checkinteger(L, 3)); |
| if (offset < 0) |
| offset = MAX(0, (int)input + offset); |
| offset += output; |
| if (offset > output + input) { |
| lua_pushfstring(L, "offset out of range."); |
| WILL_LJMP(lua_error(L)); |
| } |
| } |
| |
| ret = _hlua_http_msg_insert(msg, filter, ist2(str, sz), offset); |
| lua_pushinteger(L, ret); |
| return 1; |
| } |
| |
| /* Removes a given amount of data from the HTTP message at a given offset. By |
| * default all DATA blocks are removed. It returns the amount of data |
| * removed. Unlike the channel function used to remove data, this one can only |
| * be called inside a filter, from http_payload callback. So it cannot yield. An |
| * exception is returned if it is called from another callback. |
| */ |
| __LJMP static int hlua_http_msg_del_data(lua_State *L) |
| { |
| struct http_msg *msg; |
| struct filter *filter; |
| size_t input, output; |
| int offset, len; |
| |
| if (lua_gettop(L) < 1 || lua_gettop(L) > 3) |
| WILL_LJMP(luaL_error(L, "'remove' expects at most 2 arguments")); |
| msg = MAY_LJMP(hlua_checkhttpmsg(L, 1)); |
| |
| if (msg->msg_state < HTTP_MSG_DATA) |
| WILL_LJMP(lua_error(L)); |
| |
| filter = hlua_http_msg_filter(L, 1, msg, &output, &input); |
| if (!filter || !hlua_filter_from_payload(filter)) |
| WILL_LJMP(lua_error(L)); |
| |
| offset = output; |
| if (lua_gettop(L) > 1) { |
| offset = MAY_LJMP(luaL_checkinteger(L, 2)); |
| if (offset < 0) |
| offset = MAX(0, (int)input + offset); |
| offset += output; |
| if (offset > output + input) { |
| lua_pushfstring(L, "offset out of range."); |
| WILL_LJMP(lua_error(L)); |
| } |
| } |
| |
| len = output + input - offset; |
| if (lua_gettop(L) == 3) { |
| len = MAY_LJMP(luaL_checkinteger(L, 3)); |
| if (!len) |
| goto end; |
| if (len == -1) |
| len = output + input - offset; |
| if (len < 0 || offset + len > output + input) { |
| lua_pushfstring(L, "length out of range."); |
| WILL_LJMP(lua_error(L)); |
| } |
| } |
| |
| _hlua_http_msg_delete(msg, filter, offset, len); |
| |
| end: |
| lua_pushinteger(L, len); |
| return 1; |
| } |
| |
| /* Replaces a given amount of data at the given offset by a string. By default, |
| * all remaining data are removed, accordingly to the filter context. It returns |
| * the amount of data written or -1 if nothing was copied. Unlike the channel |
| * function used to replace data, this one can only be called inside a filter, |
| * from http_payload callback. So it cannot yield. An exception is returned if |
| * it is called from another callback. |
| */ |
| __LJMP static int hlua_http_msg_set_data(lua_State *L) |
| { |
| struct http_msg *msg; |
| struct filter *filter; |
| struct htx *htx; |
| const char *str; |
| size_t input, output, sz; |
| int offset, len; |
| int ret; |
| |
| if (lua_gettop(L) < 2 || lua_gettop(L) > 4) |
| WILL_LJMP(luaL_error(L, "'set' expects at least 1 argument and at most 3 arguments")); |
| msg = MAY_LJMP(hlua_checkhttpmsg(L, 1)); |
| |
| if (msg->msg_state < HTTP_MSG_DATA) |
| WILL_LJMP(lua_error(L)); |
| |
| str = MAY_LJMP(luaL_checklstring(L, 2, &sz)); |
| filter = hlua_http_msg_filter(L, 1, msg, &output, &input); |
| if (!filter || !hlua_filter_from_payload(filter)) |
| WILL_LJMP(lua_error(L)); |
| |
| offset = output; |
| if (lua_gettop(L) > 2) { |
| offset = MAY_LJMP(luaL_checkinteger(L, 3)); |
| if (offset < 0) |
| offset = MAX(0, (int)input + offset); |
| offset += output; |
| if (offset < output || offset > input + output) { |
| lua_pushfstring(L, "offset out of range."); |
| WILL_LJMP(lua_error(L)); |
| } |
| } |
| |
| len = output + input - offset; |
| if (lua_gettop(L) == 4) { |
| len = MAY_LJMP(luaL_checkinteger(L, 4)); |
| if (!len) |
| goto set; |
| if (len == -1) |
| len = output + input - offset; |
| if (len < 0 || offset + len > output + input) { |
| lua_pushfstring(L, "length out of range."); |
| WILL_LJMP(lua_error(L)); |
| } |
| } |
| |
| set: |
| /* Be sure we can copied the string once input data will be removed. */ |
| htx = htx_from_buf(&msg->chn->buf); |
| if (sz > htx_free_data_space(htx) + len) |
| lua_pushinteger(L, -1); |
| else { |
| _hlua_http_msg_delete(msg, filter, offset, len); |
| ret = _hlua_http_msg_insert(msg, filter, ist2(str, sz), offset); |
| lua_pushinteger(L, ret); |
| } |
| return 1; |
| } |
| |
| /* Prepends data into an HTTP message and forward it, from the filter point of |
| * view. It returns the amount of data written or -1 if nothing was sent. Unlike |
| * the channel function used to send data, this one can only be called inside a |
| * filter, from http_payload callback. So it cannot yield. An exception is |
| * returned if it is called from another callback. |
| */ |
| __LJMP static int hlua_http_msg_send(lua_State *L) |
| { |
| struct http_msg *msg; |
| struct filter *filter; |
| struct htx *htx; |
| const char *str; |
| size_t offset, len, sz; |
| int ret; |
| |
| MAY_LJMP(check_args(L, 2, "send")); |
| msg = MAY_LJMP(hlua_checkhttpmsg(L, 1)); |
| |
| if (msg->msg_state < HTTP_MSG_DATA) |
| WILL_LJMP(lua_error(L)); |
| |
| str = MAY_LJMP(luaL_checklstring(L, 2, &sz)); |
| filter = hlua_http_msg_filter(L, 1, msg, &offset, &len); |
| if (!filter || !hlua_filter_from_payload(filter)) |
| WILL_LJMP(lua_error(L)); |
| |
| /* Return an error if the channel's output is closed */ |
| if (unlikely(channel_output_closed(msg->chn))) { |
| lua_pushinteger(L, -1); |
| return 1; |
| } |
| |
| htx = htx_from_buf(&msg->chn->buf); |
| if (sz > htx_free_data_space(htx)) { |
| lua_pushinteger(L, -1); |
| return 1; |
| } |
| |
| ret = _hlua_http_msg_insert(msg, filter, ist2(str, sz), offset); |
| if (ret > 0) { |
| struct hlua_flt_ctx *flt_ctx = filter->ctx; |
| |
| FLT_OFF(filter, msg->chn) += ret; |
| flt_ctx->cur_len[CHN_IDX(msg->chn)] -= ret; |
| flt_ctx->cur_off[CHN_IDX(msg->chn)] += ret; |
| } |
| |
| lua_pushinteger(L, ret); |
| return 1; |
| } |
| |
| /* Forwards a given amount of bytes. It return -1 if the channel's output is |
| * closed. Otherwise, it returns the number of bytes forwarded. Unlike the |
| * channel function used to forward data, this one can only be called inside a |
| * filter, from http_payload callback. So it cannot yield. An exception is |
| * returned if it is called from another callback. All other functions deal with |
| * DATA block, this one not. |
| */ |
| __LJMP static int hlua_http_msg_forward(lua_State *L) |
| { |
| struct http_msg *msg; |
| struct filter *filter; |
| size_t offset, len; |
| int fwd, ret = 0; |
| |
| MAY_LJMP(check_args(L, 2, "forward")); |
| msg = MAY_LJMP(hlua_checkhttpmsg(L, 1)); |
| |
| if (msg->msg_state < HTTP_MSG_DATA) |
| WILL_LJMP(lua_error(L)); |
| |
| fwd = MAY_LJMP(luaL_checkinteger(L, 2)); |
| filter = hlua_http_msg_filter(L, 1, msg, &offset, &len); |
| if (!filter || !hlua_filter_from_payload(filter)) |
| WILL_LJMP(lua_error(L)); |
| |
| /* Nothing to do, just return */ |
| if (!fwd) |
| goto end; |
| |
| /* Return an error if the channel's output is closed */ |
| if (unlikely(channel_output_closed(msg->chn))) { |
| ret = -1; |
| goto end; |
| } |
| |
| ret = fwd; |
| if (ret > len) |
| ret = len; |
| |
| if (ret) { |
| struct hlua_flt_ctx *flt_ctx = filter->ctx; |
| |
| FLT_OFF(filter, msg->chn) += ret; |
| flt_ctx->cur_off[CHN_IDX(msg->chn)] += ret; |
| flt_ctx->cur_len[CHN_IDX(msg->chn)] -= ret; |
| } |
| |
| end: |
| lua_pushinteger(L, ret); |
| return 1; |
| } |
| |
| /* Set EOM flag on the HTX message. |
| * |
| * NOTE: Not sure it is a good idea to manipulate this flag but for now I don't |
| * really know how to do without this feature. |
| */ |
| __LJMP static int hlua_http_msg_set_eom(lua_State *L) |
| { |
| struct http_msg *msg; |
| struct htx *htx; |
| |
| MAY_LJMP(check_args(L, 1, "set_eom")); |
| msg = MAY_LJMP(hlua_checkhttpmsg(L, 1)); |
| htx = htxbuf(&msg->chn->buf); |
| htx->flags |= HTX_FL_EOM; |
| return 0; |
| } |
| |
| /* Unset EOM flag on the HTX message. |
| * |
| * NOTE: Not sure it is a good idea to manipulate this flag but for now I don't |
| * really know how to do without this feature. |
| */ |
| __LJMP static int hlua_http_msg_unset_eom(lua_State *L) |
| { |
| struct http_msg *msg; |
| struct htx *htx; |
| |
| MAY_LJMP(check_args(L, 1, "set_eom")); |
| msg = MAY_LJMP(hlua_checkhttpmsg(L, 1)); |
| htx = htxbuf(&msg->chn->buf); |
| htx->flags &= ~HTX_FL_EOM; |
| return 0; |
| } |
| |
| /* |
| * |
| * |
| * Class HTTPClient |
| * |
| * |
| */ |
| __LJMP static struct hlua_httpclient *hlua_checkhttpclient(lua_State *L, int ud) |
| { |
| return MAY_LJMP(hlua_checkudata(L, ud, class_httpclient_ref)); |
| } |
| |
| |
| /* stops the httpclient and ask it to kill itself */ |
| __LJMP static int hlua_httpclient_gc(lua_State *L) |
| { |
| struct hlua_httpclient *hlua_hc; |
| |
| MAY_LJMP(check_args(L, 1, "__gc")); |
| |
| hlua_hc = MAY_LJMP(hlua_checkhttpclient(L, 1)); |
| |
| if (MT_LIST_DELETE(&hlua_hc->by_hlua)) { |
| /* we won the race against hlua_httpclient_destroy_all() */ |
| httpclient_stop_and_destroy(hlua_hc->hc); |
| hlua_hc->hc = NULL; |
| } |
| |
| return 0; |
| } |
| |
| |
| __LJMP static int hlua_httpclient_new(lua_State *L) |
| { |
| struct hlua_httpclient *hlua_hc; |
| struct hlua *hlua; |
| |
| /* Get hlua struct, or NULL if we execute from main lua state */ |
| hlua = hlua_gethlua(L); |
| if (!hlua) |
| return 0; |
| |
| /* Check stack size. */ |
| if (!lua_checkstack(L, 3)) { |
| hlua_pusherror(L, "httpclient: full stack"); |
| goto err; |
| } |
| /* Create the object: obj[0] = userdata. */ |
| lua_newtable(L); |
| hlua_hc = MAY_LJMP(lua_newuserdata(L, sizeof(*hlua_hc))); |
| lua_rawseti(L, -2, 0); |
| memset(hlua_hc, 0, sizeof(*hlua_hc)); |
| |
| hlua_hc->hc = httpclient_new(hlua, 0, IST_NULL); |
| if (!hlua_hc->hc) |
| goto err; |
| |
| MT_LIST_APPEND(&hlua->hc_list, &hlua_hc->by_hlua); |
| |
| /* Pop a class stream metatable and affect it to the userdata. */ |
| lua_rawgeti(L, LUA_REGISTRYINDEX, class_httpclient_ref); |
| lua_setmetatable(L, -2); |
| |
| return 1; |
| |
| err: |
| WILL_LJMP(lua_error(L)); |
| return 0; |
| } |
| |
| |
| /* |
| * Callback of the httpclient, this callback wakes the lua task up, once the |
| * httpclient receives some data |
| * |
| */ |
| |
| static void hlua_httpclient_cb(struct httpclient *hc) |
| { |
| struct hlua *hlua = hc->caller; |
| |
| if (!hlua || !hlua->task) |
| return; |
| |
| task_wakeup(hlua->task, TASK_WOKEN_MSG); |
| } |
| |
| /* |
| * Fill the lua stack with headers from the httpclient response |
| * This works the same way as the hlua_http_get_headers() function |
| */ |
| __LJMP static int hlua_httpclient_get_headers(lua_State *L, struct hlua_httpclient *hlua_hc) |
| { |
| struct http_hdr *hdr; |
| |
| lua_newtable(L); |
| |
| for (hdr = hlua_hc->hc->res.hdrs; hdr && isttest(hdr->n); hdr++) { |
| struct ist n, v; |
| int len; |
| |
| n = hdr->n; |
| v = hdr->v; |
| |
| /* Check for existing entry: |
| * assume that the table is on the top of the stack, and |
| * push the key in the stack, the function lua_gettable() |
| * perform the lookup. |
| */ |
| |
| lua_pushlstring(L, n.ptr, n.len); |
| lua_gettable(L, -2); |
| |
| switch (lua_type(L, -1)) { |
| case LUA_TNIL: |
| /* Table not found, create it. */ |
| lua_pop(L, 1); /* remove the nil value. */ |
| lua_pushlstring(L, n.ptr, n.len); /* push the header name as key. */ |
| lua_newtable(L); /* create and push empty table. */ |
| lua_pushlstring(L, v.ptr, v.len); /* push header value. */ |
| lua_rawseti(L, -2, 0); /* index header value (pop it). */ |
| lua_rawset(L, -3); /* index new table with header name (pop the values). */ |
| break; |
| |
| case LUA_TTABLE: |
| /* Entry found: push the value in the table. */ |
| len = lua_rawlen(L, -1); |
| lua_pushlstring(L, v.ptr, v.len); /* push header value. */ |
| lua_rawseti(L, -2, len+1); /* index header value (pop it). */ |
| lua_pop(L, 1); /* remove the table (it is stored in the main table). */ |
| break; |
| |
| default: |
| /* Other cases are errors. */ |
| hlua_pusherror(L, "internal error during the parsing of headers."); |
| WILL_LJMP(lua_error(L)); |
| } |
| } |
| return 1; |
| } |
| |
| /* |
| * Allocate and return an array of http_hdr ist extracted from the <headers> lua table |
| * |
| * Caller must free the result |
| */ |
| struct http_hdr *hlua_httpclient_table_to_hdrs(lua_State *L) |
| { |
| struct http_hdr hdrs[global.tune.max_http_hdr]; |
| struct http_hdr *result = NULL; |
| uint32_t hdr_num = 0; |
| |
| lua_pushnil(L); |
| while (lua_next(L, -2) != 0) { |
| struct ist name, value; |
| const char *n, *v; |
| size_t nlen, vlen; |
| |
| if (!lua_isstring(L, -2) || !lua_istable(L, -1)) { |
| /* Skip element if the key is not a string or if the value is not a table */ |
| goto next_hdr; |
| } |
| |
| n = lua_tolstring(L, -2, &nlen); |
| name = ist2(n, nlen); |
| |
| /* Loop on header's values */ |
| lua_pushnil(L); |
| while (lua_next(L, -2)) { |
| if (!lua_isstring(L, -1)) { |
| /* Skip the value if it is not a string */ |
| goto next_value; |
| } |
| |
| v = lua_tolstring(L, -1, &vlen); |
| value = ist2(v, vlen); |
| name = ist2(n, nlen); |
| |
| hdrs[hdr_num].n = istdup(name); |
| hdrs[hdr_num].v = istdup(value); |
| |
| hdr_num++; |
| |
| next_value: |
| lua_pop(L, 1); |
| } |
| |
| next_hdr: |
| lua_pop(L, 1); |
| |
| } |
| |
| if (hdr_num) { |
| /* alloc and copy the headers in the httpclient struct */ |
| result = calloc((hdr_num + 1), sizeof(*result)); |
| if (!result) |
| goto skip_headers; |
| memcpy(result, hdrs, sizeof(struct http_hdr) * (hdr_num + 1)); |
| |
| result[hdr_num].n = IST_NULL; |
| result[hdr_num].v = IST_NULL; |
| } |
| |
| skip_headers: |
| |
| return result; |
| } |
| |
| |
| /* |
| * For each yield, checks if there is some data in the httpclient and push them |
| * in the lua buffer, once the httpclient finished its job, push the result on |
| * the stack |
| */ |
| __LJMP static int hlua_httpclient_rcv_yield(lua_State *L, int status, lua_KContext ctx) |
| { |
| struct buffer *tr; |
| int res; |
| struct hlua *hlua = hlua_gethlua(L); |
| struct hlua_httpclient *hlua_hc = hlua_checkhttpclient(L, 1); |
| |
| |
| tr = get_trash_chunk(); |
| |
| res = httpclient_res_xfer(hlua_hc->hc, tr); |
| luaL_addlstring(&hlua_hc->b, b_orig(tr), res); |
| |
| if (!httpclient_data(hlua_hc->hc) && httpclient_ended(hlua_hc->hc)) { |
| |
| luaL_pushresult(&hlua_hc->b); |
| lua_settable(L, -3); |
| |
| lua_pushstring(L, "status"); |
| lua_pushinteger(L, hlua_hc->hc->res.status); |
| lua_settable(L, -3); |
| |
| |
| lua_pushstring(L, "reason"); |
| lua_pushlstring(L, hlua_hc->hc->res.reason.ptr, hlua_hc->hc->res.reason.len); |
| lua_settable(L, -3); |
| |
| lua_pushstring(L, "headers"); |
| hlua_httpclient_get_headers(L, hlua_hc); |
| lua_settable(L, -3); |
| |
| return 1; |
| } |
| |
| if (httpclient_data(hlua_hc->hc)) |
| task_wakeup(hlua->task, TASK_WOKEN_MSG); |
| |
| MAY_LJMP(hlua_yieldk(L, 0, 0, hlua_httpclient_rcv_yield, TICK_ETERNITY, 0)); |
| |
| return 0; |
| } |
| |
| /* |
| * Call this when trying to stream a body during a request |
| */ |
| __LJMP static int hlua_httpclient_snd_yield(lua_State *L, int status, lua_KContext ctx) |
| { |
| struct hlua *hlua; |
| struct hlua_httpclient *hlua_hc = hlua_checkhttpclient(L, 1); |
| const char *body_str = NULL; |
| int ret; |
| int end = 0; |
| size_t buf_len; |
| size_t to_send = 0; |
| |
| hlua = hlua_gethlua(L); |
| |
| if (!hlua || !hlua->task) |
| WILL_LJMP(luaL_error(L, "The 'get' function is only allowed in " |
| "'frontend', 'backend' or 'task'")); |
| |
| ret = lua_getfield(L, -1, "body"); |
| if (ret != LUA_TSTRING) |
| goto rcv; |
| |
| body_str = lua_tolstring(L, -1, &buf_len); |
| lua_pop(L, 1); |
| |
| to_send = buf_len - hlua_hc->sent; |
| |
| if ((hlua_hc->sent + to_send) >= buf_len) |
| end = 1; |
| |
| /* the end flag is always set since we are using the whole remaining size */ |
| hlua_hc->sent += httpclient_req_xfer(hlua_hc->hc, ist2(body_str + hlua_hc->sent, to_send), end); |
| |
| if (buf_len > hlua_hc->sent) { |
| /* still need to process the buffer */ |
| MAY_LJMP(hlua_yieldk(L, 0, 0, hlua_httpclient_snd_yield, TICK_ETERNITY, 0)); |
| } else { |
| goto rcv; |
| /* we sent the whole request buffer we can recv */ |
| } |
| return 0; |
| |
| rcv: |
| |
| /* we return a "res" object */ |
| lua_newtable(L); |
| |
| lua_pushstring(L, "body"); |
| luaL_buffinit(L, &hlua_hc->b); |
| |
| task_wakeup(hlua->task, TASK_WOKEN_MSG); |
| MAY_LJMP(hlua_yieldk(L, 0, 0, hlua_httpclient_rcv_yield, TICK_ETERNITY, 0)); |
| |
| return 1; |
| } |
| |
| /* |
| * Send an HTTP request and wait for a response |
| */ |
| |
| __LJMP static int hlua_httpclient_send(lua_State *L, enum http_meth_t meth) |
| { |
| struct hlua_httpclient *hlua_hc; |
| struct http_hdr *hdrs = NULL; |
| struct http_hdr *hdrs_i = NULL; |
| struct hlua *hlua; |
| const char *url_str = NULL; |
| const char *body_str = NULL; |
| size_t buf_len = 0; |
| int ret; |
| |
| hlua = hlua_gethlua(L); |
| |
| if (!hlua || !hlua->task) |
| WILL_LJMP(luaL_error(L, "The 'get' function is only allowed in " |
| "'frontend', 'backend' or 'task'")); |
| |
| if (lua_gettop(L) != 2 || lua_type(L, -1) != LUA_TTABLE) |
| WILL_LJMP(luaL_error(L, "'get' needs a table as argument")); |
| |
| hlua_hc = hlua_checkhttpclient(L, 1); |
| |
| lua_pushnil(L); /* first key */ |
| while (lua_next(L, 2)) { |
| if (strcmp(lua_tostring(L, -2), "dst") == 0) { |
| if (httpclient_set_dst(hlua_hc->hc, lua_tostring(L, -1)) < 0) |
| WILL_LJMP(luaL_error(L, "Can't use the 'dst' argument")); |
| |
| } else if (strcmp(lua_tostring(L, -2), "url") == 0) { |
| if (lua_type(L, -1) != LUA_TSTRING) |
| WILL_LJMP(luaL_error(L, "invalid parameter in 'url', must be a string")); |
| url_str = lua_tostring(L, -1); |
| |
| } else if (strcmp(lua_tostring(L, -2), "timeout") == 0) { |
| if (lua_type(L, -1) != LUA_TNUMBER) |
| WILL_LJMP(luaL_error(L, "invalid parameter in 'timeout', must be a number")); |
| httpclient_set_timeout(hlua_hc->hc, lua_tointeger(L, -1)); |
| |
| } else if (strcmp(lua_tostring(L, -2), "headers") == 0) { |
| if (lua_type(L, -1) != LUA_TTABLE) |
| WILL_LJMP(luaL_error(L, "invalid parameter in 'headers', must be a table")); |
| hdrs = hlua_httpclient_table_to_hdrs(L); |
| |
| } else if (strcmp(lua_tostring(L, -2), "body") == 0) { |
| if (lua_type(L, -1) != LUA_TSTRING) |
| WILL_LJMP(luaL_error(L, "invalid parameter in 'body', must be a string")); |
| body_str = lua_tolstring(L, -1, &buf_len); |
| |
| } else { |
| WILL_LJMP(luaL_error(L, "'%s' invalid parameter name", lua_tostring(L, -2))); |
| } |
| /* removes 'value'; keeps 'key' for next iteration */ |
| lua_pop(L, 1); |
| } |
| |
| if (!url_str) { |
| WILL_LJMP(luaL_error(L, "'get' need a 'url' argument")); |
| return 0; |
| } |
| |
| hlua_hc->sent = 0; |
| |
| istfree(&hlua_hc->hc->req.url); |
| hlua_hc->hc->req.url = istdup(ist(url_str)); |
| hlua_hc->hc->req.meth = meth; |
| |
| /* update the httpclient callbacks */ |
| hlua_hc->hc->ops.res_stline = hlua_httpclient_cb; |
| hlua_hc->hc->ops.res_headers = hlua_httpclient_cb; |
| hlua_hc->hc->ops.res_payload = hlua_httpclient_cb; |
| hlua_hc->hc->ops.res_end = hlua_httpclient_cb; |
| |
| /* a body is available, it will use the request callback */ |
| if (body_str && buf_len) { |
| hlua_hc->hc->ops.req_payload = hlua_httpclient_cb; |
| } |
| |
| ret = httpclient_req_gen(hlua_hc->hc, hlua_hc->hc->req.url, meth, hdrs, IST_NULL); |
| |
| /* free the temporary headers array */ |
| hdrs_i = hdrs; |
| while (hdrs_i && isttest(hdrs_i->n)) { |
| istfree(&hdrs_i->n); |
| istfree(&hdrs_i->v); |
| hdrs_i++; |
| } |
| ha_free(&hdrs); |
| |
| |
| if (ret != ERR_NONE) { |
| WILL_LJMP(luaL_error(L, "Can't generate the HTTP request")); |
| return 0; |
| } |
| |
| if (!httpclient_start(hlua_hc->hc)) |
| WILL_LJMP(luaL_error(L, "couldn't start the httpclient")); |
| |
| MAY_LJMP(hlua_yieldk(L, 0, 0, hlua_httpclient_snd_yield, TICK_ETERNITY, 0)); |
| |
| return 0; |
| } |
| |
| /* |
| * Sends an HTTP HEAD request and wait for a response |
| * |
| * httpclient:head(url, headers, payload) |
| */ |
| __LJMP static int hlua_httpclient_head(lua_State *L) |
| { |
| return hlua_httpclient_send(L, HTTP_METH_HEAD); |
| } |
| |
| /* |
| * Send an HTTP GET request and wait for a response |
| * |
| * httpclient:get(url, headers, payload) |
| */ |
| __LJMP static int hlua_httpclient_get(lua_State *L) |
| { |
| return hlua_httpclient_send(L, HTTP_METH_GET); |
| |
| } |
| |
| /* |
| * Sends an HTTP PUT request and wait for a response |
| * |
| * httpclient:put(url, headers, payload) |
| */ |
| __LJMP static int hlua_httpclient_put(lua_State *L) |
| { |
| return hlua_httpclient_send(L, HTTP_METH_PUT); |
| } |
| |
| /* |
| * Send an HTTP POST request and wait for a response |
| * |
| * httpclient:post(url, headers, payload) |
| */ |
| __LJMP static int hlua_httpclient_post(lua_State *L) |
| { |
| return hlua_httpclient_send(L, HTTP_METH_POST); |
| } |
| |
| |
| /* |
| * Sends an HTTP DELETE request and wait for a response |
| * |
| * httpclient:delete(url, headers, payload) |
| */ |
| __LJMP static int hlua_httpclient_delete(lua_State *L) |
| { |
| return hlua_httpclient_send(L, HTTP_METH_DELETE); |
| } |
| |
| /* |
| * |
| * |
| * Class TXN |
| * |
| * |
| */ |
| |
| /* Returns a struct hlua_session if the stack entry "ud" is |
| * a class stream, otherwise it throws an error. |
| */ |
| __LJMP static struct hlua_txn *hlua_checktxn(lua_State *L, int ud) |
| { |
| return MAY_LJMP(hlua_checkudata(L, ud, class_txn_ref)); |
| } |
| |
| __LJMP static int hlua_set_var(lua_State *L) |
| { |
| struct hlua_txn *htxn; |
| const char *name; |
| size_t len; |
| struct sample smp; |
| |
| if (lua_gettop(L) < 3 || lua_gettop(L) > 4) |
| WILL_LJMP(luaL_error(L, "'set_var' needs between 3 and 4 arguments")); |
| |
| /* It is useles to retrieve the stream, but this function |
| * runs only in a stream context. |
| */ |
| htxn = MAY_LJMP(hlua_checktxn(L, 1)); |
| name = MAY_LJMP(luaL_checklstring(L, 2, &len)); |
| |
| /* Converts the third argument in a sample. */ |
| memset(&smp, 0, sizeof(smp)); |
| hlua_lua2smp(L, 3, &smp); |
| |
| /* Store the sample in a variable. */ |
| smp_set_owner(&smp, htxn->p, htxn->s->sess, htxn->s, htxn->dir & SMP_OPT_DIR); |
| |
| if (lua_gettop(L) == 4 && lua_toboolean(L, 4)) |
| lua_pushboolean(L, vars_set_by_name_ifexist(name, len, &smp) != 0); |
| else |
| lua_pushboolean(L, vars_set_by_name(name, len, &smp) != 0); |
| |
| return 1; |
| } |
| |
| __LJMP static int hlua_unset_var(lua_State *L) |
| { |
| struct hlua_txn *htxn; |
| const char *name; |
| size_t len; |
| struct sample smp; |
| |
| MAY_LJMP(check_args(L, 2, "unset_var")); |
| |
| /* It is useles to retrieve the stream, but this function |
| * runs only in a stream context. |
| */ |
| htxn = MAY_LJMP(hlua_checktxn(L, 1)); |
| name = MAY_LJMP(luaL_checklstring(L, 2, &len)); |
| |
| /* Unset the variable. */ |
| smp_set_owner(&smp, htxn->p, htxn->s->sess, htxn->s, htxn->dir & SMP_OPT_DIR); |
| lua_pushboolean(L, vars_unset_by_name_ifexist(name, len, &smp) != 0); |
| return 1; |
| } |
| |
| __LJMP static int hlua_get_var(lua_State *L) |
| { |
| struct hlua_txn *htxn; |
| const char *name; |
| size_t len; |
| struct sample smp; |
| |
| MAY_LJMP(check_args(L, 2, "get_var")); |
| |
| /* It is useles to retrieve the stream, but this function |
| * runs only in a stream context. |
| */ |
| htxn = MAY_LJMP(hlua_checktxn(L, 1)); |
| name = MAY_LJMP(luaL_checklstring(L, 2, &len)); |
| |
| smp_set_owner(&smp, htxn->p, htxn->s->sess, htxn->s, htxn->dir & SMP_OPT_DIR); |
| if (!vars_get_by_name(name, len, &smp, NULL)) { |
| lua_pushnil(L); |
| return 1; |
| } |
| |
| return hlua_smp2lua(L, &smp); |
| } |
| |
| __LJMP static int hlua_set_priv(lua_State *L) |
| { |
| struct hlua *hlua; |
| |
| MAY_LJMP(check_args(L, 2, "set_priv")); |
| |
| /* It is useles to retrieve the stream, but this function |
| * runs only in a stream context. |
| */ |
| MAY_LJMP(hlua_checktxn(L, 1)); |
| |
| /* Get hlua struct, or NULL if we execute from main lua state */ |
| hlua = hlua_gethlua(L); |
| if (!hlua) |
| return 0; |
| |
| /* Remove previous value. */ |
| luaL_unref(L, LUA_REGISTRYINDEX, hlua->Mref); |
| |
| /* Get and store new value. */ |
| lua_pushvalue(L, 2); /* Copy the element 2 at the top of the stack. */ |
| hlua->Mref = luaL_ref(L, LUA_REGISTRYINDEX); /* pop the previously pushed value. */ |
| |
| return 0; |
| } |
| |
| __LJMP static int hlua_get_priv(lua_State *L) |
| { |
| struct hlua *hlua; |
| |
| MAY_LJMP(check_args(L, 1, "get_priv")); |
| |
| /* It is useles to retrieve the stream, but this function |
| * runs only in a stream context. |
| */ |
| MAY_LJMP(hlua_checktxn(L, 1)); |
| |
| /* Get hlua struct, or NULL if we execute from main lua state */ |
| hlua = hlua_gethlua(L); |
| if (!hlua) { |
| lua_pushnil(L); |
| return 1; |
| } |
| |
| /* Push configuration index in the stack. */ |
| lua_rawgeti(L, LUA_REGISTRYINDEX, hlua->Mref); |
| |
| return 1; |
| } |
| |
| /* Create stack entry containing a class TXN. This function |
| * return 0 if the stack does not contains free slots, |
| * otherwise it returns 1. |
| */ |
| static int hlua_txn_new(lua_State *L, struct stream *s, struct proxy *p, int dir, int flags) |
| { |
| struct hlua_txn *htxn; |
| |
| /* Check stack size. */ |
| if (!lua_checkstack(L, 3)) |
| return 0; |
| |
| /* NOTE: The allocation never fails. The failure |
| * throw an error, and the function never returns. |
| * if the throw is not available, the process is aborted. |
| */ |
| /* Create the object: obj[0] = userdata. */ |
| lua_newtable(L); |
| htxn = lua_newuserdata(L, sizeof(*htxn)); |
| lua_rawseti(L, -2, 0); |
| |
| htxn->s = s; |
| htxn->p = p; |
| htxn->dir = dir; |
| htxn->flags = flags; |
| |
| /* Create the "f" field that contains a list of fetches. */ |
| lua_pushstring(L, "f"); |
| if (!hlua_fetches_new(L, htxn, HLUA_F_MAY_USE_HTTP)) |
| return 0; |
| lua_rawset(L, -3); |
| |
| /* Create the "sf" field that contains a list of stringsafe fetches. */ |
| lua_pushstring(L, "sf"); |
| if (!hlua_fetches_new(L, htxn, HLUA_F_MAY_USE_HTTP | HLUA_F_AS_STRING)) |
| return 0; |
| lua_rawset(L, -3); |
| |
| /* Create the "c" field that contains a list of converters. */ |
| lua_pushstring(L, "c"); |
| if (!hlua_converters_new(L, htxn, 0)) |
| return 0; |
| lua_rawset(L, -3); |
| |
| /* Create the "sc" field that contains a list of stringsafe converters. */ |
| lua_pushstring(L, "sc"); |
| if (!hlua_converters_new(L, htxn, HLUA_F_AS_STRING)) |
| return 0; |
| lua_rawset(L, -3); |
| |
| /* Create the "req" field that contains the request channel object. */ |
| lua_pushstring(L, "req"); |
| if (!hlua_channel_new(L, &s->req)) |
| return 0; |
| lua_rawset(L, -3); |
| |
| /* Create the "res" field that contains the response channel object. */ |
| lua_pushstring(L, "res"); |
| if (!hlua_channel_new(L, &s->res)) |
| return 0; |
| lua_rawset(L, -3); |
| |
| /* Creates the HTTP object is the current proxy allows http. */ |
| lua_pushstring(L, "http"); |
| if (IS_HTX_STRM(s)) { |
| if (!hlua_http_new(L, htxn)) |
| return 0; |
| } |
| else |
| lua_pushnil(L); |
| lua_rawset(L, -3); |
| |
| if ((htxn->flags & HLUA_TXN_CTX_MASK) == HLUA_TXN_FLT_CTX) { |
| /* HTTPMessage object are created when a lua TXN is created from |
| * a filter context only |
| */ |
| |
| /* Creates the HTTP-Request object is the current proxy allows http. */ |
| lua_pushstring(L, "http_req"); |
| if (p->mode == PR_MODE_HTTP) { |
| if (!hlua_http_msg_new(L, &s->txn->req)) |
| return 0; |
| } |
| else |
| lua_pushnil(L); |
| lua_rawset(L, -3); |
| |
| /* Creates the HTTP-Response object is the current proxy allows http. */ |
| lua_pushstring(L, "http_res"); |
| if (p->mode == PR_MODE_HTTP) { |
| if (!hlua_http_msg_new(L, &s->txn->rsp)) |
| return 0; |
| } |
| else |
| lua_pushnil(L); |
| lua_rawset(L, -3); |
| } |
| |
| /* Pop a class sesison metatable and affect it to the userdata. */ |
| lua_rawgeti(L, LUA_REGISTRYINDEX, class_txn_ref); |
| lua_setmetatable(L, -2); |
| |
| return 1; |
| } |
| |
| __LJMP static int hlua_txn_deflog(lua_State *L) |
| { |
| const char *msg; |
| struct hlua_txn *htxn; |
| |
| MAY_LJMP(check_args(L, 2, "deflog")); |
| htxn = MAY_LJMP(hlua_checktxn(L, 1)); |
| msg = MAY_LJMP(luaL_checkstring(L, 2)); |
| |
| hlua_sendlog(htxn->s->be, htxn->s->logs.level, msg); |
| return 0; |
| } |
| |
| __LJMP static int hlua_txn_log(lua_State *L) |
| { |
| int level; |
| const char *msg; |
| struct hlua_txn *htxn; |
| |
| MAY_LJMP(check_args(L, 3, "log")); |
| htxn = MAY_LJMP(hlua_checktxn(L, 1)); |
| level = MAY_LJMP(luaL_checkinteger(L, 2)); |
| msg = MAY_LJMP(luaL_checkstring(L, 3)); |
| |
| if (level < 0 || level >= NB_LOG_LEVELS) |
| WILL_LJMP(luaL_argerror(L, 1, "Invalid loglevel.")); |
| |
| hlua_sendlog(htxn->s->be, level, msg); |
| return 0; |
| } |
| |
| __LJMP static int hlua_txn_log_debug(lua_State *L) |
| { |
| const char *msg; |
| struct hlua_txn *htxn; |
| |
| MAY_LJMP(check_args(L, 2, "Debug")); |
| htxn = MAY_LJMP(hlua_checktxn(L, 1)); |
| msg = MAY_LJMP(luaL_checkstring(L, 2)); |
| hlua_sendlog(htxn->s->be, LOG_DEBUG, msg); |
| return 0; |
| } |
| |
| __LJMP static int hlua_txn_log_info(lua_State *L) |
| { |
| const char *msg; |
| struct hlua_txn *htxn; |
| |
| MAY_LJMP(check_args(L, 2, "Info")); |
| htxn = MAY_LJMP(hlua_checktxn(L, 1)); |
| msg = MAY_LJMP(luaL_checkstring(L, 2)); |
| hlua_sendlog(htxn->s->be, LOG_INFO, msg); |
| return 0; |
| } |
| |
| __LJMP static int hlua_txn_log_warning(lua_State *L) |
| { |
| const char *msg; |
| struct hlua_txn *htxn; |
| |
| MAY_LJMP(check_args(L, 2, "Warning")); |
| htxn = MAY_LJMP(hlua_checktxn(L, 1)); |
| msg = MAY_LJMP(luaL_checkstring(L, 2)); |
| hlua_sendlog(htxn->s->be, LOG_WARNING, msg); |
| return 0; |
| } |
| |
| __LJMP static int hlua_txn_log_alert(lua_State *L) |
| { |
| const char *msg; |
| struct hlua_txn *htxn; |
| |
| MAY_LJMP(check_args(L, 2, "Alert")); |
| htxn = MAY_LJMP(hlua_checktxn(L, 1)); |
| msg = MAY_LJMP(luaL_checkstring(L, 2)); |
| hlua_sendlog(htxn->s->be, LOG_ALERT, msg); |
| return 0; |
| } |
| |
| __LJMP static int hlua_txn_set_loglevel(lua_State *L) |
| { |
| struct hlua_txn *htxn; |
| int ll; |
| |
| MAY_LJMP(check_args(L, 2, "set_loglevel")); |
| htxn = MAY_LJMP(hlua_checktxn(L, 1)); |
| ll = MAY_LJMP(luaL_checkinteger(L, 2)); |
| |
| if (ll < 0 || ll > 7) |
| WILL_LJMP(luaL_argerror(L, 2, "Bad log level. It must be between 0 and 7")); |
| |
| htxn->s->logs.level = ll; |
| return 0; |
| } |
| |
| __LJMP static int hlua_txn_set_tos(lua_State *L) |
| { |
| struct hlua_txn *htxn; |
| int tos; |
| |
| MAY_LJMP(check_args(L, 2, "set_tos")); |
| htxn = MAY_LJMP(hlua_checktxn(L, 1)); |
| tos = MAY_LJMP(luaL_checkinteger(L, 2)); |
| |
| conn_set_tos(objt_conn(htxn->s->sess->origin), tos); |
| return 0; |
| } |
| |
| __LJMP static int hlua_txn_set_mark(lua_State *L) |
| { |
| struct hlua_txn *htxn; |
| int mark; |
| |
| MAY_LJMP(check_args(L, 2, "set_mark")); |
| htxn = MAY_LJMP(hlua_checktxn(L, 1)); |
| mark = MAY_LJMP(luaL_checkinteger(L, 2)); |
| |
| conn_set_mark(objt_conn(htxn->s->sess->origin), mark); |
| return 0; |
| } |
| |
| __LJMP static int hlua_txn_set_priority_class(lua_State *L) |
| { |
| struct hlua_txn *htxn; |
| |
| MAY_LJMP(check_args(L, 2, "set_priority_class")); |
| htxn = MAY_LJMP(hlua_checktxn(L, 1)); |
| htxn->s->priority_class = queue_limit_class(MAY_LJMP(luaL_checkinteger(L, 2))); |
| return 0; |
| } |
| |
| __LJMP static int hlua_txn_set_priority_offset(lua_State *L) |
| { |
| struct hlua_txn *htxn; |
| |
| MAY_LJMP(check_args(L, 2, "set_priority_offset")); |
| htxn = MAY_LJMP(hlua_checktxn(L, 1)); |
| htxn->s->priority_offset = queue_limit_offset(MAY_LJMP(luaL_checkinteger(L, 2))); |
| return 0; |
| } |
| |
| /* Forward the Reply object to the client. This function converts the reply in |
| * HTX an push it to into the response channel. It is response to forward the |
| * message and terminate the transaction. It returns 1 on success and 0 on |
| * error. The Reply must be on top of the stack. |
| */ |
| __LJMP static int hlua_txn_forward_reply(lua_State *L, struct stream *s) |
| { |
| struct htx *htx; |
| struct htx_sl *sl; |
| struct h1m h1m; |
| const char *status, *reason, *body; |
| size_t status_len, reason_len, body_len; |
| int ret, code, flags; |
| |
| code = 200; |
| status = "200"; |
| status_len = 3; |
| ret = lua_getfield(L, -1, "status"); |
| if (ret == LUA_TNUMBER) { |
| code = lua_tointeger(L, -1); |
| status = lua_tolstring(L, -1, &status_len); |
| } |
| lua_pop(L, 1); |
| |
| reason = http_get_reason(code); |
| reason_len = strlen(reason); |
| ret = lua_getfield(L, -1, "reason"); |
| if (ret == LUA_TSTRING) |
| reason = lua_tolstring(L, -1, &reason_len); |
| lua_pop(L, 1); |
| |
| body = NULL; |
| body_len = 0; |
| ret = lua_getfield(L, -1, "body"); |
| if (ret == LUA_TSTRING) |
| body = lua_tolstring(L, -1, &body_len); |
| lua_pop(L, 1); |
| |
| /* Prepare the response before inserting the headers */ |
| h1m_init_res(&h1m); |
| htx = htx_from_buf(&s->res.buf); |
| channel_htx_truncate(&s->res, htx); |
| if (s->txn->req.flags & HTTP_MSGF_VER_11) { |
| flags = (HTX_SL_F_IS_RESP|HTX_SL_F_VER_11); |
| sl = htx_add_stline(htx, HTX_BLK_RES_SL, flags, ist("HTTP/1.1"), |
| ist2(status, status_len), ist2(reason, reason_len)); |
| } |
| else { |
| flags = HTX_SL_F_IS_RESP; |
| sl = htx_add_stline(htx, HTX_BLK_RES_SL, flags, ist("HTTP/1.0"), |
| ist2(status, status_len), ist2(reason, reason_len)); |
| } |
| if (!sl) |
| goto fail; |
| sl->info.res.status = code; |
| |
| /* Push in the stack the "headers" entry. */ |
| ret = lua_getfield(L, -1, "headers"); |
| if (ret != LUA_TTABLE) |
| goto skip_headers; |
| |
| lua_pushnil(L); |
| while (lua_next(L, -2) != 0) { |
| struct ist name, value; |
| const char *n, *v; |
| size_t nlen, vlen; |
| |
| if (!lua_isstring(L, -2) || !lua_istable(L, -1)) { |
| /* Skip element if the key is not a string or if the value is not a table */ |
| goto next_hdr; |
| } |
| |
| n = lua_tolstring(L, -2, &nlen); |
| name = ist2(n, nlen); |
| if (isteqi(name, ist("content-length"))) { |
| /* Always skip content-length header. It will be added |
| * later with the correct len |
| */ |
| goto next_hdr; |
| } |
| |
| /* Loop on header's values */ |
| lua_pushnil(L); |
| while (lua_next(L, -2)) { |
| if (!lua_isstring(L, -1)) { |
| /* Skip the value if it is not a string */ |
| goto next_value; |
| } |
| |
| v = lua_tolstring(L, -1, &vlen); |
| value = ist2(v, vlen); |
| |
| if (isteqi(name, ist("transfer-encoding"))) |
| h1_parse_xfer_enc_header(&h1m, value); |
| if (!htx_add_header(htx, ist2(n, nlen), ist2(v, vlen))) |
| goto fail; |
| |
| next_value: |
| lua_pop(L, 1); |
| } |
| |
| next_hdr: |
| lua_pop(L, 1); |
| } |
| skip_headers: |
| lua_pop(L, 1); |
| |
| /* Update h1m flags: CLEN is set if CHNK is not present */ |
| if (!(h1m.flags & H1_MF_CHNK)) { |
| const char *clen = ultoa(body_len); |
| |
| h1m.flags |= H1_MF_CLEN; |
| if (!htx_add_header(htx, ist("content-length"), ist(clen))) |
| goto fail; |
| } |
| if (h1m.flags & (H1_MF_CLEN|H1_MF_CHNK)) |
| h1m.flags |= H1_MF_XFER_LEN; |
| |
| /* Update HTX start-line flags */ |
| if (h1m.flags & H1_MF_XFER_ENC) |
| flags |= HTX_SL_F_XFER_ENC; |
| if (h1m.flags & H1_MF_XFER_LEN) { |
| flags |= HTX_SL_F_XFER_LEN; |
| if (h1m.flags & H1_MF_CHNK) |
| flags |= HTX_SL_F_CHNK; |
| else if (h1m.flags & H1_MF_CLEN) |
| flags |= HTX_SL_F_CLEN; |
| if (h1m.body_len == 0) |
| flags |= HTX_SL_F_BODYLESS; |
| } |
| sl->flags |= flags; |
| |
| |
| if (!htx_add_endof(htx, HTX_BLK_EOH) || |
| (body_len && !htx_add_data_atonce(htx, ist2(body, body_len)))) |
| goto fail; |
| |
| htx->flags |= HTX_FL_EOM; |
| |
| /* Now, forward the response and terminate the transaction */ |
| s->txn->status = code; |
| htx_to_buf(htx, &s->res.buf); |
| if (!http_forward_proxy_resp(s, 1)) |
| goto fail; |
| |
| return 1; |
| |
| fail: |
| channel_htx_truncate(&s->res, htx); |
| return 0; |
| } |
| |
| /* Terminate a transaction if called from a lua action. For TCP streams, |
| * processing is just aborted. Nothing is returned to the client and all |
| * arguments are ignored. For HTTP streams, if a reply is passed as argument, it |
| * is forwarded to the client before terminating the transaction. On success, |
| * the function exits with ACT_RET_DONE code. If an error occurred, it exits |
| * with ACT_RET_ERR code. If this function is not called from a lua action, it |
| * just exits without any processing. |
| */ |
| __LJMP static int hlua_txn_done(lua_State *L) |
| { |
| struct hlua_txn *htxn; |
| struct stream *s; |
| int finst; |
| |
| htxn = MAY_LJMP(hlua_checktxn(L, 1)); |
| |
| /* If the flags NOTERM is set, we cannot terminate the session, so we |
| * just end the execution of the current lua code. */ |
| if (htxn->flags & HLUA_TXN_NOTERM) |
| WILL_LJMP(hlua_done(L)); |
| |
| s = htxn->s; |
| if (!IS_HTX_STRM(htxn->s)) { |
| struct channel *req = &s->req; |
| struct channel *res = &s->res; |
| |
| channel_auto_read(req); |
| channel_abort(req); |
| channel_erase(req); |
| |
| channel_auto_read(res); |
| channel_auto_close(res); |
| sc_schedule_abort(s->scb); |
| |
| finst = ((htxn->dir == SMP_OPT_DIR_REQ) ? SF_FINST_R : SF_FINST_D); |
| goto done; |
| } |
| |
| if (lua_gettop(L) == 1 || !lua_istable(L, 2)) { |
| /* No reply or invalid reply */ |
| s->txn->status = 0; |
| http_reply_and_close(s, 0, NULL); |
| } |
| else { |
| /* Remove extra args to have the reply on top of the stack */ |
| if (lua_gettop(L) > 2) |
| lua_pop(L, lua_gettop(L) - 2); |
| |
| if (!hlua_txn_forward_reply(L, s)) { |
| if (!(s->flags & SF_ERR_MASK)) |
| s->flags |= SF_ERR_PRXCOND; |
| lua_pushinteger(L, ACT_RET_ERR); |
| WILL_LJMP(hlua_done(L)); |
| return 0; /* Never reached */ |
| } |
| } |
| |
| finst = ((htxn->dir == SMP_OPT_DIR_REQ) ? SF_FINST_R : SF_FINST_H); |
| if (htxn->dir == SMP_OPT_DIR_REQ) { |
| /* let's log the request time */ |
| s->logs.request_ts = now_ns; |
| if (s->sess->fe == s->be) /* report it if the request was intercepted by the frontend */ |
| _HA_ATOMIC_INC(&s->sess->fe->fe_counters.intercepted_req); |
| } |
| |
| done: |
| if (!(s->flags & SF_ERR_MASK)) |
| s->flags |= SF_ERR_LOCAL; |
| if (!(s->flags & SF_FINST_MASK)) |
| s->flags |= finst; |
| |
| if ((htxn->flags & HLUA_TXN_CTX_MASK) == HLUA_TXN_FLT_CTX) |
| lua_pushinteger(L, -1); |
| else |
| lua_pushinteger(L, ACT_RET_ABRT); |
| WILL_LJMP(hlua_done(L)); |
| return 0; |
| } |
| |
| /* |
| * |
| * |
| * Class REPLY |
| * |
| * |
| */ |
| |
| /* Pushes the TXN reply onto the top of the stack. If the stask does not have a |
| * free slots, the function fails and returns 0; |
| */ |
| static int hlua_txn_reply_new(lua_State *L) |
| { |
| struct hlua_txn *htxn; |
| const char *reason, *body = NULL; |
| int ret, status; |
| |
| htxn = MAY_LJMP(hlua_checktxn(L, 1)); |
| if (!IS_HTX_STRM(htxn->s)) { |
| hlua_pusherror(L, "txn object is not an HTTP transaction."); |
| WILL_LJMP(lua_error(L)); |
| } |
| |
| /* Default value */ |
| status = 200; |
| reason = http_get_reason(status); |
| |
| if (lua_istable(L, 2)) { |
| /* load status and reason from the table argument at index 2 */ |
| ret = lua_getfield(L, 2, "status"); |
| if (ret == LUA_TNIL) |
| goto reason; |
| else if (ret != LUA_TNUMBER) { |
| /* invalid status: ignore the reason */ |
| goto body; |
| } |
| status = lua_tointeger(L, -1); |
| |
| reason: |
| lua_pop(L, 1); /* restore the stack: remove status */ |
| ret = lua_getfield(L, 2, "reason"); |
| if (ret == LUA_TSTRING) |
| reason = lua_tostring(L, -1); |
| |
| body: |
| lua_pop(L, 1); /* restore the stack: remove invalid status or reason */ |
| ret = lua_getfield(L, 2, "body"); |
| if (ret == LUA_TSTRING) |
| body = lua_tostring(L, -1); |
| lua_pop(L, 1); /* restore the stack: remove body */ |
| } |
| |
| /* Create the Reply table */ |
| lua_newtable(L); |
| |
| /* Add status element */ |
| lua_pushstring(L, "status"); |
| lua_pushinteger(L, status); |
| lua_settable(L, -3); |
| |
| /* Add reason element */ |
| reason = http_get_reason(status); |
| lua_pushstring(L, "reason"); |
| lua_pushstring(L, reason); |
| lua_settable(L, -3); |
| |
| /* Add body element, nil if undefined */ |
| lua_pushstring(L, "body"); |
| if (body) |
| lua_pushstring(L, body); |
| else |
| lua_pushnil(L); |
| lua_settable(L, -3); |
| |
| /* Add headers element */ |
| lua_pushstring(L, "headers"); |
| lua_newtable(L); |
| |
| /* stack: [ txn, <Arg:table>, <Reply:table>, "headers", <headers:table> ] */ |
| if (lua_istable(L, 2)) { |
| /* load headers from the table argument at index 2. If it is a table, copy it. */ |
| ret = lua_getfield(L, 2, "headers"); |
| if (ret == LUA_TTABLE) { |
| /* stack: [ ... <headers:table>, <table> ] */ |
| lua_pushnil(L); |
| while (lua_next(L, -2) != 0) { |
| /* stack: [ ... <headers:table>, <table>, k, v] */ |
| if (!lua_isstring(L, -1) && !lua_istable(L, -1)) { |
| /* invalid value type, skip it */ |
| lua_pop(L, 1); |
| continue; |
| } |
| |
| |
| /* Duplicate the key and swap it with the value. */ |
| lua_pushvalue(L, -2); |
| lua_insert(L, -2); |
| /* stack: [ ... <headers:table>, <table>, k, k, v ] */ |
| |
| lua_newtable(L); |
| lua_insert(L, -2); |
| /* stack: [ ... <headers:table>, <table>, k, k, <inner:table>, v ] */ |
| |
| if (lua_isstring(L, -1)) { |
| /* push the value in the inner table */ |
| lua_rawseti(L, -2, 1); |
| } |
| else { /* table */ |
| lua_pushnil(L); |
| while (lua_next(L, -2) != 0) { |
| /* stack: [ ... <headers:table>, <table>, k, k, <inner:table>, <v:table>, k2, v2 ] */ |
| if (!lua_isstring(L, -1)) { |
| /* invalid value type, skip it*/ |
| lua_pop(L, 1); |
| continue; |
| } |
| /* push the value in the inner table */ |
| lua_rawseti(L, -4, lua_rawlen(L, -4) + 1); |
| /* stack: [ ... <headers:table>, <table>, k, k, <inner:table>, <v:table>, k2 ] */ |
| } |
| lua_pop(L, 1); |
| /* stack: [ ... <headers:table>, <table>, k, k, <inner:table> ] */ |
| } |
| |
| /* push (k,v) on the stack in the headers table: |
| * stack: [ ... <headers:table>, <table>, k, k, v ] |
| */ |
| lua_settable(L, -5); |
| /* stack: [ ... <headers:table>, <table>, k ] */ |
| } |
| } |
| lua_pop(L, 1); |
| } |
| /* stack: [ txn, <Arg:table>, <Reply:table>, "headers", <headers:table> ] */ |
| lua_settable(L, -3); |
| /* stack: [ txn, <Arg:table>, <Reply:table> ] */ |
| |
| /* Pop a class sesison metatable and affect it to the userdata. */ |
| lua_rawgeti(L, LUA_REGISTRYINDEX, class_txn_reply_ref); |
| lua_setmetatable(L, -2); |
| return 1; |
| } |
| |
| /* Set the reply status code, and optionally the reason. If no reason is |
| * provided, the default one corresponding to the status code is used. |
| */ |
| __LJMP static int hlua_txn_reply_set_status(lua_State *L) |
| { |
| int status = MAY_LJMP(luaL_checkinteger(L, 2)); |
| const char *reason = MAY_LJMP(luaL_optlstring(L, 3, NULL, NULL)); |
| |
| /* First argument (self) must be a table */ |
| MAY_LJMP(luaL_checktype(L, 1, LUA_TTABLE)); |
| |
| if (status < 100 || status > 599) { |
| lua_pushboolean(L, 0); |
| return 1; |
| } |
| if (!reason) |
| reason = http_get_reason(status); |
| |
| lua_pushinteger(L, status); |
| lua_setfield(L, 1, "status"); |
| |
| lua_pushstring(L, reason); |
| lua_setfield(L, 1, "reason"); |
| |
| lua_pushboolean(L, 1); |
| return 1; |
| } |
| |
| /* Add a header into the reply object. Each header name is associated to an |
| * array of values in the "headers" table. If the header name is not found, a |
| * new entry is created. |
| */ |
| __LJMP static int hlua_txn_reply_add_header(lua_State *L) |
| { |
| const char *name = MAY_LJMP(luaL_checkstring(L, 2)); |
| const char *value = MAY_LJMP(luaL_checkstring(L, 3)); |
| int ret; |
| |
| /* First argument (self) must be a table */ |
| MAY_LJMP(luaL_checktype(L, 1, LUA_TTABLE)); |
| |
| /* Push in the stack the "headers" entry. */ |
| ret = lua_getfield(L, 1, "headers"); |
| if (ret != LUA_TTABLE) { |
| hlua_pusherror(L, "Reply['headers'] is expected to a an array. %s found", lua_typename(L, ret)); |
| WILL_LJMP(lua_error(L)); |
| } |
| |
| /* check if the header is already registered. If not, register it. */ |
| ret = lua_getfield(L, -1, name); |
| if (ret == LUA_TNIL) { |
| /* Entry not found. */ |
| lua_pop(L, 1); /* remove the nil. The "headers" table is the top of the stack. */ |
| |
| /* Insert the new header name in the array in the top of the stack. |
| * It left the new array in the top of the stack. |
| */ |
| lua_newtable(L); |
| lua_pushstring(L, name); |
| lua_pushvalue(L, -2); |
| lua_settable(L, -4); |
| } |
| else if (ret != LUA_TTABLE) { |
| hlua_pusherror(L, "Reply['headers']['%s'] is expected to be an array. %s found", name, lua_typename(L, ret)); |
| WILL_LJMP(lua_error(L)); |
| } |
| |
| /* Now the top of thestack is an array of values. We push |
| * the header value as new entry. |
| */ |
| lua_pushstring(L, value); |
| ret = lua_rawlen(L, -2); |
| lua_rawseti(L, -2, ret + 1); |
| |
| lua_pushboolean(L, 1); |
| return 1; |
| } |
| |
| /* Remove all occurrences of a given header name. */ |
| __LJMP static int hlua_txn_reply_del_header(lua_State *L) |
| { |
| const char *name = MAY_LJMP(luaL_checkstring(L, 2)); |
| int ret; |
| |
| /* First argument (self) must be a table */ |
| MAY_LJMP(luaL_checktype(L, 1, LUA_TTABLE)); |
| |
| /* Push in the stack the "headers" entry. */ |
| ret = lua_getfield(L, 1, "headers"); |
| if (ret != LUA_TTABLE) { |
| hlua_pusherror(L, "Reply['headers'] is expected to be an array. %s found", lua_typename(L, ret)); |
| WILL_LJMP(lua_error(L)); |
| } |
| |
| lua_pushstring(L, name); |
| lua_pushnil(L); |
| lua_settable(L, -3); |
| |
| lua_pushboolean(L, 1); |
| return 1; |
| } |
| |
| /* Set the reply's body. Overwrite any existing entry. */ |
| __LJMP static int hlua_txn_reply_set_body(lua_State *L) |
| { |
| const char *payload = MAY_LJMP(luaL_checkstring(L, 2)); |
| |
| /* First argument (self) must be a table */ |
| MAY_LJMP(luaL_checktype(L, 1, LUA_TTABLE)); |
| |
| lua_pushstring(L, payload); |
| lua_setfield(L, 1, "body"); |
| |
| lua_pushboolean(L, 1); |
| return 1; |
| } |
| |
| __LJMP static int hlua_log(lua_State *L) |
| { |
| int level; |
| const char *msg; |
| |
| MAY_LJMP(check_args(L, 2, "log")); |
| level = MAY_LJMP(luaL_checkinteger(L, 1)); |
| msg = MAY_LJMP(luaL_checkstring(L, 2)); |
| |
| if (level < 0 || level >= NB_LOG_LEVELS) |
| WILL_LJMP(luaL_argerror(L, 1, "Invalid loglevel.")); |
| |
| hlua_sendlog(NULL, level, msg); |
| return 0; |
| } |
| |
| __LJMP static int hlua_log_debug(lua_State *L) |
| { |
| const char *msg; |
| |
| MAY_LJMP(check_args(L, 1, "debug")); |
| msg = MAY_LJMP(luaL_checkstring(L, 1)); |
| hlua_sendlog(NULL, LOG_DEBUG, msg); |
| return 0; |
| } |
| |
| __LJMP static int hlua_log_info(lua_State *L) |
| { |
| const char *msg; |
| |
| MAY_LJMP(check_args(L, 1, "info")); |
| msg = MAY_LJMP(luaL_checkstring(L, 1)); |
| hlua_sendlog(NULL, LOG_INFO, msg); |
| return 0; |
| } |
| |
| __LJMP static int hlua_log_warning(lua_State *L) |
| { |
| const char *msg; |
| |
| MAY_LJMP(check_args(L, 1, "warning")); |
| msg = MAY_LJMP(luaL_checkstring(L, 1)); |
| hlua_sendlog(NULL, LOG_WARNING, msg); |
| return 0; |
| } |
| |
| __LJMP static int hlua_log_alert(lua_State *L) |
| { |
| const char *msg; |
| |
| MAY_LJMP(check_args(L, 1, "alert")); |
| msg = MAY_LJMP(luaL_checkstring(L, 1)); |
| hlua_sendlog(NULL, LOG_ALERT, msg); |
| return 0; |
| } |
| |
| __LJMP static int hlua_sleep_yield(lua_State *L, int status, lua_KContext ctx) |
| { |
| int wakeup_ms = lua_tointeger(L, -1); |
| if (!tick_is_expired(wakeup_ms, now_ms)) |
| MAY_LJMP(hlua_yieldk(L, 0, 0, hlua_sleep_yield, wakeup_ms, 0)); |
| return 0; |
| } |
| |
| __LJMP static int hlua_sleep(lua_State *L) |
| { |
| unsigned int delay; |
| int wakeup_ms; // tick value |
| |
| MAY_LJMP(check_args(L, 1, "sleep")); |
| |
| delay = MAY_LJMP(luaL_checkinteger(L, 1)) * 1000; |
| wakeup_ms = tick_add(now_ms, delay); |
| lua_pushinteger(L, wakeup_ms); |
| |
| MAY_LJMP(hlua_yieldk(L, 0, 0, hlua_sleep_yield, wakeup_ms, 0)); |
| return 0; |
| } |
| |
| __LJMP static int hlua_msleep(lua_State *L) |
| { |
| unsigned int delay; |
| int wakeup_ms; // tick value |
| |
| MAY_LJMP(check_args(L, 1, "msleep")); |
| |
| delay = MAY_LJMP(luaL_checkinteger(L, 1)); |
| wakeup_ms = tick_add(now_ms, delay); |
| lua_pushinteger(L, wakeup_ms); |
| |
| MAY_LJMP(hlua_yieldk(L, 0, 0, hlua_sleep_yield, wakeup_ms, 0)); |
| return 0; |
| } |
| |
| /* This functionis an LUA binding. it permits to give back |
| * the hand at the HAProxy scheduler. It is used when the |
| * LUA processing consumes a lot of time. |
| */ |
| __LJMP static int hlua_yield_yield(lua_State *L, int status, lua_KContext ctx) |
| { |
| return 0; |
| } |
| |
| __LJMP static int hlua_yield(lua_State *L) |
| { |
| MAY_LJMP(hlua_yieldk(L, 0, 0, hlua_yield_yield, TICK_ETERNITY, HLUA_CTRLYIELD)); |
| return 0; |
| } |
| |
| /* This function change the nice of the currently executed |
| * task. It is used set low or high priority at the current |
| * task. |
| */ |
| __LJMP static int hlua_set_nice(lua_State *L) |
| { |
| struct hlua *hlua; |
| int nice; |
| |
| MAY_LJMP(check_args(L, 1, "set_nice")); |
| nice = MAY_LJMP(luaL_checkinteger(L, 1)); |
| |
| /* Get hlua struct, or NULL if we execute from main lua state */ |
| hlua = hlua_gethlua(L); |
| |
| /* If the task is not set, I'm in a start mode. */ |
| if (!hlua || !hlua->task) |
| return 0; |
| |
| if (nice < -1024) |
| nice = -1024; |
| else if (nice > 1024) |
| nice = 1024; |
| |
| hlua->task->nice = nice; |
| return 0; |
| } |
| |
| /* safe lua coroutine.create() function: |
| * |
| * This is a simple wrapper for coroutine.create() that |
| * ensures the current hlua state ctx is available from |
| * the new subroutine state |
| */ |
| __LJMP static int hlua_coroutine_create(lua_State *L) |
| { |
| lua_State *new; /* new coroutine state */ |
| struct hlua **hlua_store; |
| struct hlua *hlua = hlua_gethlua(L); |
| |
| new = lua_newthread(L); |
| if (!new) |
| return 0; |
| |
| hlua_store = lua_getextraspace(new); |
| /* Expose current hlua ctx on new lua thread |
| * (hlua_gethlua() will properly return the last "known" |
| * hlua ctx instead of NULL when it is called from such coroutines) |
| */ |
| *hlua_store = hlua; |
| |
| /* new lua thread is on the top of the stack, we |
| * need to duplicate first stack argument (<f> from coroutine.create(<f>)) |
| * on the top of the stack to be able to use xmove() to move it on the new |
| * stack |
| */ |
| lua_pushvalue(L, 1); |
| /* move <f> function to the new stack */ |
| lua_xmove(L, new, 1); |
| /* new lua thread is back at the top of the stack */ |
| return 1; |
| } |
| |
| /* This function is used as a callback of a task. It is called by the |
| * HAProxy task subsystem when the task is awaked. The LUA runtime can |
| * return an E_AGAIN signal, the emmiter of this signal must set a |
| * signal to wake the task. |
| * |
| * Task wrapper are longjmp safe because the only one Lua code |
| * executed is the safe hlua_ctx_resume(); |
| */ |
| struct task *hlua_process_task(struct task *task, void *context, unsigned int state) |
| { |
| struct hlua *hlua = context; |
| enum hlua_exec status; |
| |
| if (task->tid < 0) |
| task->tid = tid; |
| |
| /* If it is the first call to the task, we must initialize the |
| * execution timeouts. |
| */ |
| if (!HLUA_IS_RUNNING(hlua)) |
| hlua_timer_init(&hlua->timer, hlua_timeout_task); |
| |
| /* Execute the Lua code. */ |
| status = hlua_ctx_resume(hlua, 1); |
| |
| switch (status) { |
| /* finished or yield */ |
| case HLUA_E_OK: |
| hlua_ctx_destroy(hlua); |
| task_destroy(task); |
| task = NULL; |
| break; |
| |
| case HLUA_E_AGAIN: /* co process or timeout wake me later. */ |
| notification_gc(&hlua->com); |
| task->expire = hlua->wake_time; |
| break; |
| |
| /* finished with error. */ |
| case HLUA_E_ETMOUT: |
| SEND_ERR(NULL, "Lua task: execution timeout.\n"); |
| goto err_task_abort; |
| case HLUA_E_ERRMSG: |
| SEND_ERR(NULL, "Lua task: %s.\n", lua_tostring(hlua->T, -1)); |
| goto err_task_abort; |
| case HLUA_E_ERR: |
| default: |
| SEND_ERR(NULL, "Lua task: unknown error.\n"); |
| err_task_abort: |
| hlua_ctx_destroy(hlua); |
| task_destroy(task); |
| task = NULL; |
| break; |
| } |
| return task; |
| } |
| |
| /* This function is an LUA binding that register LUA function to be |
| * executed after the HAProxy configuration parsing and before the |
| * HAProxy scheduler starts. This function expect only one LUA |
| * argument that is a function. This function returns nothing, but |
| * throws if an error is encountered. |
| */ |
| __LJMP static int hlua_register_init(lua_State *L) |
| { |
| struct hlua_init_function *init; |
| int ref; |
| |
| MAY_LJMP(check_args(L, 1, "register_init")); |
| |
| if (hlua_gethlua(L)) { |
| /* runtime processing */ |
| WILL_LJMP(luaL_error(L, "register_init: not available outside of body context")); |
| } |
| |
| ref = MAY_LJMP(hlua_checkfunction(L, 1)); |
| |
| init = calloc(1, sizeof(*init)); |
| if (!init) { |
| hlua_unref(L, ref); |
| WILL_LJMP(luaL_error(L, "Lua out of memory error.")); |
| } |
| |
| init->function_ref = ref; |
| LIST_APPEND(&hlua_init_functions[hlua_state_id], &init->l); |
| return 0; |
| } |
| |
| /* This function is an LUA binding. It permits to register a task |
| * executed in parallel of the main HAroxy activity. The task is |
| * created and it is set in the HAProxy scheduler. It can be called |
| * from the "init" section, "post init" or during the runtime. |
| * |
| * Lua prototype: |
| * |
| * <none> core.register_task(<function>[, <arg1>[, <arg2>[, ...[, <arg4>]]]]) |
| * |
| * <arg1..4> are optional arguments that will be provided to <function> |
| */ |
| static int hlua_register_task(lua_State *L) |
| { |
| struct hlua *hlua = NULL; |
| struct task *task = NULL; |
| int ref; |
| int nb_arg; |
| int it; |
| int arg_ref[4]; /* optional arguments */ |
| int state_id; |
| |
| nb_arg = lua_gettop(L); |
| if (nb_arg < 1) |
| WILL_LJMP(luaL_error(L, "register_task: <func> argument is required")); |
| else if (nb_arg > 5) |
| WILL_LJMP(luaL_error(L, "register_task: no more that 4 optional arguments may be provided")); |
| |
| /* first arg: function ref */ |
| ref = MAY_LJMP(hlua_checkfunction(L, 1)); |
| |
| /* extract optional args (if any) */ |
| it = 0; |
| while (--nb_arg) { |
| lua_pushvalue(L, 2 + it); |
| arg_ref[it] = hlua_ref(L); /* get arg reference */ |
| it += 1; |
| } |
| nb_arg = it; |
| |
| /* Get the reference state. If the reference is NULL, L is the master |
| * state, otherwise hlua->T is. |
| */ |
| hlua = hlua_gethlua(L); |
| if (hlua) |
| /* we are in runtime processing */ |
| state_id = hlua->state_id; |
| else |
| /* we are in initialization mode */ |
| state_id = hlua_state_id; |
| |
| hlua = pool_alloc(pool_head_hlua); |
| if (!hlua) |
| goto alloc_error; |
| HLUA_INIT(hlua); |
| |
| /* We are in the common lua state, execute the task anywhere, |
| * otherwise, inherit the current thread identifier |
| */ |
| if (state_id == 0) |
| task = task_new_anywhere(); |
| else |
| task = task_new_here(); |
| if (!task) |
| goto alloc_error; |
| |
| task->context = hlua; |
| task->process = hlua_process_task; |
| |
| if (!hlua_ctx_init(hlua, state_id, task)) |
| goto alloc_error; |
| |
| /* Ensure there is enough space on the stack for the function |
| * plus optional arguments |
| */ |
| if (!lua_checkstack(hlua->T, (1 + nb_arg))) |
| goto alloc_error; |
| |
| /* Restore the function in the stack. */ |
| hlua_pushref(hlua->T, ref); |
| /* function ref not needed anymore since it was pushed to the substack */ |
| hlua_unref(L, ref); |
| |
| hlua->nargs = nb_arg; |
| |
| /* push optional arguments to the function */ |
| for (it = 0; it < nb_arg; it++) { |
| /* push arg to the stack */ |
| hlua_pushref(hlua->T, arg_ref[it]); |
| /* arg ref not needed anymore since it was pushed to the substack */ |
| hlua_unref(L, arg_ref[it]); |
| } |
| |
| /* Schedule task. */ |
| task_wakeup(task, TASK_WOKEN_INIT); |
| |
| return 0; |
| |
| alloc_error: |
| task_destroy(task); |
| hlua_unref(L, ref); |
| for (it = 0; it < nb_arg; it++) { |
| hlua_unref(L, arg_ref[it]); |
| } |
| hlua_ctx_destroy(hlua); |
| WILL_LJMP(luaL_error(L, "Lua out of memory error.")); |
| return 0; /* Never reached */ |
| } |
| |
| /* called from unsafe location */ |
| static void hlua_event_subscription_destroy(struct hlua_event_sub *hlua_sub) |
| { |
| /* hlua cleanup */ |
| |
| hlua_lock(hlua_sub->hlua); |
| /* registry is shared between coroutines */ |
| hlua_unref(hlua_sub->hlua->T, hlua_sub->fcn_ref); |
| hlua_unlock(hlua_sub->hlua); |
| |
| hlua_ctx_destroy(hlua_sub->hlua); |
| |
| /* free */ |
| pool_free(pool_head_hlua_event_sub, hlua_sub); |
| } |
| |
| /* single event handler: hlua ctx is shared between multiple events handlers |
| * issued from the same subscription. Thus, it is not destroyed when the event |
| * is processed: it is destroyed when no more events are expected for the |
| * subscription (ie: when the subscription ends). |
| * |
| * Moreover, events are processed sequentially within the subscription: |
| * one event must be fully processed before another one may be processed. |
| * This ensures proper consistency for lua event handling from an ordering |
| * point of view. This is especially useful with server events for example |
| * where ADD/DEL/UP/DOWN events ordering really matters to trigger specific |
| * actions from lua (e.g.: sending emails or making API calls). |
| * |
| * Due to this design, each lua event handler is pleased to process the event |
| * as fast as possible to prevent the event queue from growing up. |
| * Strictly speaking, there is no runtime limit for the callback function |
| * (timeout set to default task timeout), but if the event queue goes past |
| * the limit of unconsumed events an error will be reported and the |
| * susbscription will pause itself for as long as it takes for the handler to |
| * catch up (events will be lost as a result). |
| * If the event handler does not need the sequential ordering and wants to |
| * process multiple events at a time, it may spawn a new side-task using |
| * 'core.register_task' to delegate the event handling and make parallel event |
| * processing within the same subscription set. |
| */ |
| static void hlua_event_handler(struct hlua *hlua) |
| { |
| enum hlua_exec status; |
| |
| /* If it is the first call to the task, we must initialize the |
| * execution timeouts. |
| */ |
| if (!HLUA_IS_RUNNING(hlua)) |
| hlua_timer_init(&hlua->timer, hlua_timeout_task); |
| |
| /* make sure to reset the task expiry before each hlua_ctx_resume() |
| * since the task is re-used for multiple cb function calls |
| * We couldn't risk to have t->expire pointing to a past date because |
| * it was set during last function invocation but was never reset since |
| * (ie: E_AGAIN) |
| */ |
| hlua->task->expire = TICK_ETERNITY; |
| |
| /* Execute the Lua code. */ |
| status = hlua_ctx_resume(hlua, 1); |
| |
| switch (status) { |
| /* finished or yield */ |
| case HLUA_E_OK: |
| break; |
| |
| case HLUA_E_AGAIN: /* co process or timeout wake me later. */ |
| notification_gc(&hlua->com); |
| hlua->task->expire = hlua->wake_time; |
| break; |
| |
| /* finished with error. */ |
| case HLUA_E_ETMOUT: |
| SEND_ERR(NULL, "Lua event_hdl: execution timeout.\n"); |
| break; |
| |
| case HLUA_E_ERRMSG: |
| SEND_ERR(NULL, "Lua event_hdl: %s.\n", lua_tostring(hlua->T, -1)); |
| break; |
| |
| case HLUA_E_ERR: |
| default: |
| SEND_ERR(NULL, "Lua event_hdl: unknown error.\n"); |
| break; |
| } |
| } |
| |
| __LJMP static void hlua_event_hdl_cb_push_event_checkres(lua_State *L, |
| struct event_hdl_cb_data_server_checkres *check) |
| { |
| lua_pushstring(L, "agent"); |
| lua_pushboolean(L, check->agent); |
| lua_settable(L, -3); |
| lua_pushstring(L, "result"); |
| switch (check->result) { |
| case CHK_RES_FAILED: |
| lua_pushstring(L, "FAILED"); |
| break; |
| case CHK_RES_PASSED: |
| lua_pushstring(L, "PASSED"); |
| break; |
| case CHK_RES_CONDPASS: |
| lua_pushstring(L, "CONDPASS"); |
| break; |
| default: |
| lua_pushnil(L); |
| break; |
| } |
| lua_settable(L, -3); |
| |
| lua_pushstring(L, "duration"); |
| lua_pushinteger(L, check->duration); |
| lua_settable(L, -3); |
| |
| lua_pushstring(L, "reason"); |
| lua_newtable(L); |
| |
| lua_pushstring(L, "short"); |
| lua_pushstring(L, get_check_status_info(check->reason.status)); |
| lua_settable(L, -3); |
| lua_pushstring(L, "desc"); |
| lua_pushstring(L, get_check_status_description(check->reason.status)); |
| lua_settable(L, -3); |
| if (check->reason.status >= HCHK_STATUS_L57DATA) { |
| /* code only available when the check reached data analysis stage */ |
| lua_pushstring(L, "code"); |
| lua_pushinteger(L, check->reason.code); |
| lua_settable(L, -3); |
| } |
| |
| lua_settable(L, -3); /* reason table */ |
| |
| lua_pushstring(L, "health"); |
| lua_newtable(L); |
| |
| lua_pushstring(L, "cur"); |
| lua_pushinteger(L, check->health.cur); |
| lua_settable(L, -3); |
| lua_pushstring(L, "rise"); |
| lua_pushinteger(L, check->health.rise); |
| lua_settable(L, -3); |
| lua_pushstring(L, "fall"); |
| lua_pushinteger(L, check->health.fall); |
| lua_settable(L, -3); |
| |
| lua_settable(L, -3); /* health table */ |
| } |
| |
| /* This function pushes various arguments such as event type and event data to |
| * the lua function that will be called to consume the event. |
| */ |
| __LJMP static void hlua_event_hdl_cb_push_args(struct hlua_event_sub *hlua_sub, |
| struct event_hdl_async_event *e) |
| { |
| struct hlua *hlua = hlua_sub->hlua; |
| struct event_hdl_sub_type event = e->type; |
| void *data = e->data; |
| |
| /* push event type */ |
| hlua->nargs = 1; |
| lua_pushstring(hlua->T, event_hdl_sub_type_to_string(event)); |
| |
| /* push event data (according to event type) */ |
| if (event_hdl_sub_family_equal(EVENT_HDL_SUB_SERVER, event)) { |
| struct event_hdl_cb_data_server *e_server = data; |
| struct proxy *px; |
| struct server *server; |
| |
| hlua->nargs += 1; |
| lua_newtable(hlua->T); |
| /* Add server name */ |
| lua_pushstring(hlua->T, "name"); |
| lua_pushstring(hlua->T, e_server->safe.name); |
| lua_settable(hlua->T, -3); |
| /* Add server puid */ |
| lua_pushstring(hlua->T, "puid"); |
| lua_pushinteger(hlua->T, e_server->safe.puid); |
| lua_settable(hlua->T, -3); |
| /* Add server rid */ |
| lua_pushstring(hlua->T, "rid"); |
| lua_pushinteger(hlua->T, e_server->safe.rid); |
| lua_settable(hlua->T, -3); |
| /* Add server proxy name */ |
| lua_pushstring(hlua->T, "proxy_name"); |
| lua_pushstring(hlua->T, e_server->safe.proxy_name); |
| lua_settable(hlua->T, -3); |
| /* Add server proxy uuid */ |
| lua_pushstring(hlua->T, "proxy_uuid"); |
| lua_pushinteger(hlua->T, e_server->safe.proxy_uuid); |
| lua_settable(hlua->T, -3); |
| |
| /* special events, fetch additional info with explicit type casting */ |
| if (event_hdl_sub_type_equal(EVENT_HDL_SUB_SERVER_STATE, event)) { |
| struct event_hdl_cb_data_server_state *state = data; |
| int it; |
| |
| if (!lua_checkstack(hlua->T, 20)) |
| WILL_LJMP(luaL_error(hlua->T, "Lua out of memory error.")); |
| |
| /* state subclass */ |
| lua_pushstring(hlua->T, "state"); |
| lua_newtable(hlua->T); |
| |
| lua_pushstring(hlua->T, "admin"); |
| lua_pushboolean(hlua->T, state->safe.type); |
| lua_settable(hlua->T, -3); |
| |
| /* is it because of a check ? */ |
| if (!state->safe.type && |
| (state->safe.op_st_chg.cause == SRV_OP_STCHGC_HEALTH || |
| state->safe.op_st_chg.cause == SRV_OP_STCHGC_AGENT)) { |
| /* yes, provide check result */ |
| lua_pushstring(hlua->T, "check"); |
| lua_newtable(hlua->T); |
| hlua_event_hdl_cb_push_event_checkres(hlua->T, &state->safe.op_st_chg.check); |
| lua_settable(hlua->T, -3); /* check table */ |
| } |
| |
| lua_pushstring(hlua->T, "cause"); |
| if (state->safe.type) |
| lua_pushstring(hlua->T, srv_adm_st_chg_cause(state->safe.adm_st_chg.cause)); |
| else |
| lua_pushstring(hlua->T, srv_op_st_chg_cause(state->safe.op_st_chg.cause)); |
| lua_settable(hlua->T, -3); |
| |
| /* old_state, new_state */ |
| for (it = 0; it < 2; it++) { |
| enum srv_state srv_state = (!it) ? state->safe.old_state : state->safe.new_state; |
| |
| lua_pushstring(hlua->T, (!it) ? "old_state" : "new_state"); |
| switch (srv_state) { |
| case SRV_ST_STOPPED: |
| lua_pushstring(hlua->T, "STOPPED"); |
| break; |
| case SRV_ST_STOPPING: |
| lua_pushstring(hlua->T, "STOPPING"); |
| break; |
| case SRV_ST_STARTING: |
| lua_pushstring(hlua->T, "STARTING"); |
| break; |
| case SRV_ST_RUNNING: |
| lua_pushstring(hlua->T, "RUNNING"); |
| break; |
| default: |
| lua_pushnil(hlua->T); |
| break; |
| } |
| lua_settable(hlua->T, -3); |
| } |
| |
| /* requeued */ |
| lua_pushstring(hlua->T, "requeued"); |
| lua_pushinteger(hlua->T, state->safe.requeued); |
| lua_settable(hlua->T, -3); |
| |
| lua_settable(hlua->T, -3); /* state table */ |
| } |
| else if (event_hdl_sub_type_equal(EVENT_HDL_SUB_SERVER_ADMIN, event)) { |
| struct event_hdl_cb_data_server_admin *admin = data; |
| int it; |
| |
| if (!lua_checkstack(hlua->T, 20)) |
| WILL_LJMP(luaL_error(hlua->T, "Lua out of memory error.")); |
| |
| /* admin subclass */ |
| lua_pushstring(hlua->T, "admin"); |
| lua_newtable(hlua->T); |
| |
| lua_pushstring(hlua->T, "cause"); |
| lua_pushstring(hlua->T, srv_adm_st_chg_cause(admin->safe.cause)); |
| lua_settable(hlua->T, -3); |
| |
| /* old_admin, new_admin */ |
| for (it = 0; it < 2; it++) { |
| enum srv_admin srv_admin = (!it) ? admin->safe.old_admin : admin->safe.new_admin; |
| |
| lua_pushstring(hlua->T, (!it) ? "old_admin" : "new_admin"); |
| |
| /* admin state matrix */ |
| lua_newtable(hlua->T); |
| |
| lua_pushstring(hlua->T, "MAINT"); |
| lua_pushboolean(hlua->T, srv_admin & SRV_ADMF_MAINT); |
| lua_settable(hlua->T, -3); |
| lua_pushstring(hlua->T, "FMAINT"); |
| lua_pushboolean(hlua->T, srv_admin & SRV_ADMF_FMAINT); |
| lua_settable(hlua->T, -3); |
| lua_pushstring(hlua->T, "IMAINT"); |
| lua_pushboolean(hlua->T, srv_admin & SRV_ADMF_IMAINT); |
| lua_settable(hlua->T, -3); |
| lua_pushstring(hlua->T, "RMAINT"); |
| lua_pushboolean(hlua->T, srv_admin & SRV_ADMF_RMAINT); |
| lua_settable(hlua->T, -3); |
| lua_pushstring(hlua->T, "CMAINT"); |
| lua_pushboolean(hlua->T, srv_admin & SRV_ADMF_CMAINT); |
| lua_settable(hlua->T, -3); |
| |
| lua_pushstring(hlua->T, "DRAIN"); |
| lua_pushboolean(hlua->T, srv_admin & SRV_ADMF_DRAIN); |
| lua_settable(hlua->T, -3); |
| lua_pushstring(hlua->T, "FDRAIN"); |
| lua_pushboolean(hlua->T, srv_admin & SRV_ADMF_FDRAIN); |
| lua_settable(hlua->T, -3); |
| lua_pushstring(hlua->T, "IDRAIN"); |
| lua_pushboolean(hlua->T, srv_admin & SRV_ADMF_IDRAIN); |
| lua_settable(hlua->T, -3); |
| |
| lua_settable(hlua->T, -3); /* matrix table */ |
| } |
| /* requeued */ |
| lua_pushstring(hlua->T, "requeued"); |
| lua_pushinteger(hlua->T, admin->safe.requeued); |
| lua_settable(hlua->T, -3); |
| |
| lua_settable(hlua->T, -3); /* admin table */ |
| } |
| |
| /* attempt to provide reference server object |
| * (if it wasn't removed yet, SERVER_DEL will never succeed here) |
| */ |
| px = proxy_find_by_id(e_server->safe.proxy_uuid, PR_CAP_BE, 0); |
| BUG_ON(!px); |
| server = findserver_unique_id(px, e_server->safe.puid, e_server->safe.rid); |
| if (server) { |
| lua_pushstring(hlua->T, "reference"); |
| hlua_fcn_new_server(hlua->T, server); |
| lua_settable(hlua->T, -3); |
| } |
| } |
| /* sub mgmt */ |
| hlua->nargs += 1; |
| hlua_fcn_new_event_sub(hlua->T, hlua_sub->sub); |
| |
| /* when? */ |
| hlua->nargs += 1; |
| lua_pushinteger(hlua->T, e->when.tv_sec); |
| } |
| |
| /* events runner: if there's an ongoing hlua event handling process, finish it |
| * then, check if there are new events waiting to be processed |
| * (events are processed sequentially) |
| * |
| * We have a safety measure to warn/guard if the event queue is growing up |
| * too much due to many events being generated and lua handler is unable to |
| * keep up the pace (e.g.: when the event queue grows past 100 unconsumed events). |
| * TODO: make it tunable |
| */ |
| static struct task *hlua_event_runner(struct task *task, void *context, unsigned int state) |
| { |
| struct hlua_event_sub *hlua_sub = context; |
| struct event_hdl_async_event *event; |
| const char *error = NULL; |
| |
| if (!hlua_sub->paused && event_hdl_async_equeue_size(&hlua_sub->equeue) > 100) { |
| const char *trace; |
| |
| /* We reached the limit of pending events in the queue: we should |
| * warn the user, and temporarily pause the subscription to give a chance |
| * to the handler to catch up? (it also prevents resource shortage since |
| * the queue could grow indefinitely otherwise) |
| * TODO: find a way to inform the handler that it missed some events |
| * (example: stats within the subscription in event_hdl api exposed via lua api?) |
| * |
| * Nonetheless, reaching this limit means that the handler is not fast enough |
| * and/or that it subscribed to events that happen too frequently and did not |
| * expect it. This could come from an inadequate design in the user's script. |
| */ |
| event_hdl_pause(hlua_sub->sub); |
| hlua_sub->paused = 1; |
| |
| /* The following Lua calls can fail. */ |
| if (!SET_SAFE_LJMP(hlua_sub->hlua)) |
| trace = NULL; |
| trace = hlua_traceback(hlua_sub->hlua->T, ", "); |
| /* At this point the execution is safe. */ |
| RESET_SAFE_LJMP(hlua_sub->hlua); |
| |
| ha_warning("Lua event_hdl: pausing the subscription because the function fails to keep up the pace (%u unconsumed events): %s\n", event_hdl_async_equeue_size(&hlua_sub->equeue), ((trace) ? trace : "")); |
| } |
| |
| if (HLUA_IS_RUNNING(hlua_sub->hlua)) { |
| /* ongoing hlua event handler, resume it */ |
| hlua_event_handler(hlua_sub->hlua); |
| } else if ((event = event_hdl_async_equeue_pop(&hlua_sub->equeue))) { /* check for new events */ |
| if (event_hdl_sub_type_equal(event->type, EVENT_HDL_SUB_END)) { |
| /* ending event: no more events to come */ |
| event_hdl_async_free_event(event); |
| task_destroy(task); |
| hlua_event_subscription_destroy(hlua_sub); |
| return NULL; |
| } |
| /* new event: start processing it */ |
| |
| /* The following Lua calls can fail. */ |
| if (!SET_SAFE_LJMP(hlua_sub->hlua)) { |
| if (lua_type(hlua_sub->hlua->T, -1) == LUA_TSTRING) |
| error = lua_tostring(hlua_sub->hlua->T, -1); |
| else |
| error = "critical error"; |
| ha_alert("Lua event_hdl: %s.\n", error); |
| goto skip_event; |
| } |
| |
| /* Check stack available size. */ |
| if (!lua_checkstack(hlua_sub->hlua->T, 5)) { |
| ha_alert("Lua event_hdl: full stack.\n"); |
| RESET_SAFE_LJMP(hlua_sub->hlua); |
| goto skip_event; |
| } |
| |
| /* Restore the function in the stack. */ |
| hlua_pushref(hlua_sub->hlua->T, hlua_sub->fcn_ref); |
| |
| /* push args */ |
| hlua_sub->hlua->nargs = 0; |
| MAY_LJMP(hlua_event_hdl_cb_push_args(hlua_sub, event)); |
| |
| /* At this point the execution is safe. */ |
| RESET_SAFE_LJMP(hlua_sub->hlua); |
| |
| /* At this point the event was successfully translated into hlua ctx, |
| * or hlua error occurred, so we can safely discard it |
| */ |
| event_hdl_async_free_event(event); |
| event = NULL; |
| |
| hlua_event_handler(hlua_sub->hlua); |
| skip_event: |
| if (event) |
| event_hdl_async_free_event(event); |
| |
| } |
| |
| if (!HLUA_IS_RUNNING(hlua_sub->hlua)) { |
| /* we just finished the processing of one event.. |
| * check for new events before becoming idle |
| */ |
| if (!event_hdl_async_equeue_isempty(&hlua_sub->equeue)) { |
| /* more events to process, make sure the task |
| * will be resumed ASAP to process pending events |
| */ |
| task_wakeup(task, TASK_WOKEN_OTHER); |
| } |
| else if (hlua_sub->paused) { |
| /* empty queue, the handler caught up: resume the subscription */ |
| event_hdl_resume(hlua_sub->sub); |
| hlua_sub->paused = 0; |
| } |
| } |
| |
| return task; |
| } |
| |
| /* Must be called directly under lua protected/safe environment |
| * (not from external callback) |
| * <fcn_ref> should NOT be dropped after the function successfully returns: |
| * it will be done automatically in hlua_event_subscription_destroy() when the |
| * subscription ends. |
| * |
| * Returns the new subscription on success and NULL on failure (memory error) |
| */ |
| static struct event_hdl_sub *hlua_event_subscribe(event_hdl_sub_list *list, struct event_hdl_sub_type e_type, |
| int state_id, int fcn_ref) |
| { |
| struct hlua_event_sub *hlua_sub; |
| struct task *task = NULL; |
| |
| hlua_sub = pool_alloc(pool_head_hlua_event_sub); |
| if (!hlua_sub) |
| goto mem_error; |
| hlua_sub->task = NULL; |
| hlua_sub->hlua = NULL; |
| hlua_sub->paused = 0; |
| if ((task = task_new_here()) == NULL) { |
| ha_alert("out of memory while allocating hlua event task"); |
| goto mem_error; |
| } |
| task->process = hlua_event_runner; |
| task->context = hlua_sub; |
| event_hdl_async_equeue_init(&hlua_sub->equeue); |
| hlua_sub->task = task; |
| hlua_sub->fcn_ref = fcn_ref; |
| hlua_sub->state_id = state_id; |
| hlua_sub->hlua = pool_alloc(pool_head_hlua); |
| if (!hlua_sub->hlua) |
| goto mem_error; |
| HLUA_INIT(hlua_sub->hlua); |
| if (!hlua_ctx_init(hlua_sub->hlua, hlua_sub->state_id, task)) |
| goto mem_error; |
| |
| hlua_sub->sub = event_hdl_subscribe_ptr(list, e_type, |
| EVENT_HDL_ASYNC_TASK(&hlua_sub->equeue, |
| task, |
| hlua_sub, |
| NULL)); |
| if (!hlua_sub->sub) |
| goto mem_error; |
| |
| return hlua_sub->sub; /* returns pointer to event_hdl_sub struct */ |
| |
| mem_error: |
| if (hlua_sub) { |
| task_destroy(hlua_sub->task); |
| if (hlua_sub->hlua) |
| hlua_ctx_destroy(hlua_sub->hlua); |
| pool_free(pool_head_hlua_event_sub, hlua_sub); |
| } |
| |
| return NULL; |
| } |
| |
| /* looks for an array of strings referring to a composition of event_hdl subscription |
| * types at <index> in <L> stack |
| */ |
| __LJMP static struct event_hdl_sub_type hlua_check_event_sub_types(lua_State *L, int index) |
| { |
| struct event_hdl_sub_type subscriptions; |
| const char *msg; |
| |
| if (lua_type(L, index) != LUA_TTABLE) { |
| msg = lua_pushfstring(L, "table of strings expected, got %s", luaL_typename(L, index)); |
| luaL_argerror(L, index, msg); |
| } |
| |
| subscriptions = EVENT_HDL_SUB_NONE; |
| |
| /* browse the argument as an array. */ |
| lua_pushnil(L); |
| while (lua_next(L, index) != 0) { |
| if (lua_type(L, -1) != LUA_TSTRING) { |
| msg = lua_pushfstring(L, "table of strings expected, got %s", luaL_typename(L, index)); |
| luaL_argerror(L, index, msg); |
| } |
| |
| if (event_hdl_sub_type_equal(EVENT_HDL_SUB_NONE, event_hdl_string_to_sub_type(lua_tostring(L, -1)))) { |
| msg = lua_pushfstring(L, "'%s' event type is unknown", lua_tostring(L, -1)); |
| luaL_argerror(L, index, msg); |
| } |
| |
| /* perform subscriptions |= current sub */ |
| subscriptions = event_hdl_sub_type_add(subscriptions, event_hdl_string_to_sub_type(lua_tostring(L, -1))); |
| |
| /* pop the current value. */ |
| lua_pop(L, 1); |
| } |
| |
| return subscriptions; |
| } |
| |
| /* Wrapper for hlua_fcn_new_event_sub(): catch errors raised by |
| * the function to prevent LJMP |
| * |
| * If no error occurred, the function returns 1, else it returns 0 and |
| * the error message is pushed at the top of the stack |
| */ |
| __LJMP static int _hlua_new_event_sub_safe(lua_State *L) |
| { |
| struct event_hdl_sub *sub = lua_touserdata(L, 1); |
| |
| /* this function may raise errors */ |
| return MAY_LJMP(hlua_fcn_new_event_sub(L, sub)); |
| } |
| static int hlua_new_event_sub_safe(lua_State *L, struct event_hdl_sub *sub) |
| { |
| if (!lua_checkstack(L, 2)) |
| return 0; |
| lua_pushcfunction(L, _hlua_new_event_sub_safe); |
| lua_pushlightuserdata(L, sub); |
| switch (lua_pcall(L, 1, 1, 0)) { |
| case LUA_OK: |
| return 1; |
| default: |
| /* error was caught */ |
| return 0; |
| } |
| } |
| |
| /* This function is a LUA helper used for registering lua event callbacks. |
| * It expects an event subscription array and the function to be executed |
| * when subscribed events occur (stack arguments). |
| * It can be called from the "init" section, "post init" or during the runtime. |
| * |
| * <sub_list> is the subscription list where the subscription will be attempted |
| * |
| * Pushes the newly allocated subscription on the stack on success |
| */ |
| __LJMP int hlua_event_sub(lua_State *L, event_hdl_sub_list *sub_list) |
| { |
| struct hlua *hlua; |
| struct event_hdl_sub *sub; |
| struct event_hdl_sub_type subscriptions; |
| int fcn_ref; |
| int state_id; |
| |
| MAY_LJMP(check_args(L, 2, "event_sub")); |
| |
| /* Get the reference state */ |
| hlua = hlua_gethlua(L); |
| if (hlua) |
| /* we are in runtime processing, any thread may subscribe to events: |
| * subscription events will be handled by the thread who performed |
| * the registration. |
| */ |
| state_id = hlua->state_id; |
| else { |
| /* we are in initialization mode, only thread 0 (actual calling thread) |
| * may subscribe to events to prevent the same handler (from different lua |
| * stacks) from being registered multiple times |
| * |
| * hlua_state_id == 0: monostack (lua-load) |
| * hlua_state_id > 0: hlua_state_id=tid+1, multi-stack (lua-load-per-thread) |
| * (thus if hlua_state_id > 1, it means we are not in primary thread ctx) |
| */ |
| if (hlua_state_id > 1) |
| return 0; /* skip registration */ |
| state_id = hlua_state_id; |
| } |
| |
| /* First argument : event subscriptions. */ |
| subscriptions = MAY_LJMP(hlua_check_event_sub_types(L, 1)); |
| |
| if (event_hdl_sub_type_equal(subscriptions, EVENT_HDL_SUB_NONE)) { |
| WILL_LJMP(luaL_error(L, "event_sub: no valid event types were provided")); |
| return 0; /* Never reached */ |
| } |
| |
| /* Second argument : lua function. */ |
| fcn_ref = MAY_LJMP(hlua_checkfunction(L, 2)); |
| |
| /* try to subscribe */ |
| sub = hlua_event_subscribe(sub_list, subscriptions, state_id, fcn_ref); |
| if (!sub) { |
| hlua_unref(L, fcn_ref); |
| WILL_LJMP(luaL_error(L, "event_sub: lua out of memory error")); |
| return 0; /* Never reached */ |
| } |
| |
| /* push the subscription to the stack |
| * |
| * Here we use the safe function so that lua errors will be |
| * handled explicitly to prevent 'sub' from being lost |
| */ |
| if (!hlua_new_event_sub_safe(L, sub)) { |
| /* Some events could already be pending in the handler's queue. |
| * However it is wiser to cancel the subscription since we are unable to |
| * provide a valid reference to it. |
| * Pending events will be delivered (unless lua keeps raising errors). |
| */ |
| event_hdl_unsubscribe(sub); /* cancel the subscription */ |
| WILL_LJMP(luaL_error(L, "event_sub: cannot push the subscription (%s)", lua_tostring(L, -1))); |
| return 0; /* Never reached */ |
| } |
| event_hdl_drop(sub); /* sub has been duplicated, discard old ref */ |
| |
| return 1; |
| } |
| |
| /* This function is a LUA wrapper used for registering global lua event callbacks |
| * The new subscription is pushed onto the stack on success |
| * Returns the number of arguments pushed to the stack (1 for success) |
| */ |
| __LJMP static int hlua_event_global_sub(lua_State *L) |
| { |
| /* NULL <sub_list> = global subscription list */ |
| return MAY_LJMP(hlua_event_sub(L, NULL)); |
| } |
| |
| /* Wrapper called by HAProxy to execute an LUA converter. This wrapper |
| * doesn't allow "yield" functions because the HAProxy engine cannot |
| * resume converters. |
| */ |
| static int hlua_sample_conv_wrapper(const struct arg *arg_p, struct sample *smp, void *private) |
| { |
| struct hlua_function *fcn = private; |
| struct stream *stream = smp->strm; |
| const char *error; |
| |
| if (!stream) |
| return 0; |
| |
| /* In the execution wrappers linked with a stream, the |
| * Lua context can be not initialized. This behavior |
| * permits to save performances because a systematic |
| * Lua initialization cause 5% performances loss. |
| */ |
| if (!stream->hlua) { |
| struct hlua *hlua; |
| |
| hlua = pool_alloc(pool_head_hlua); |
| if (!hlua) { |
| SEND_ERR(stream->be, "Lua converter '%s': can't initialize Lua context.\n", fcn->name); |
| return 0; |
| } |
| HLUA_INIT(hlua); |
| stream->hlua = hlua; |
| if (!hlua_ctx_init(stream->hlua, fcn_ref_to_stack_id(fcn), stream->task)) { |
| SEND_ERR(stream->be, "Lua converter '%s': can't initialize Lua context.\n", fcn->name); |
| return 0; |
| } |
| } |
| |
| /* If it is the first run, initialize the data for the call. */ |
| if (!HLUA_IS_RUNNING(stream->hlua)) { |
| |
| /* The following Lua calls can fail. */ |
| if (!SET_SAFE_LJMP(stream->hlua)) { |
| if (lua_type(stream->hlua->T, -1) == LUA_TSTRING) |
| error = lua_tostring(stream->hlua->T, -1); |
| else |
| error = "critical error"; |
| SEND_ERR(stream->be, "Lua converter '%s': %s.\n", fcn->name, error); |
| return 0; |
| } |
| |
| /* Check stack available size. */ |
| if (!lua_checkstack(stream->hlua->T, 1)) { |
| SEND_ERR(stream->be, "Lua converter '%s': full stack.\n", fcn->name); |
| RESET_SAFE_LJMP(stream->hlua); |
| return 0; |
| } |
| |
| /* Restore the function in the stack. */ |
| hlua_pushref(stream->hlua->T, fcn->function_ref[stream->hlua->state_id]); |
| |
| /* convert input sample and pust-it in the stack. */ |
| if (!lua_checkstack(stream->hlua->T, 1)) { |
| SEND_ERR(stream->be, "Lua converter '%s': full stack.\n", fcn->name); |
| RESET_SAFE_LJMP(stream->hlua); |
| return 0; |
| } |
| hlua_smp2lua(stream->hlua->T, smp); |
| stream->hlua->nargs = 1; |
| |
| /* push keywords in the stack. */ |
| if (arg_p) { |
| for (; arg_p->type != ARGT_STOP; arg_p++) { |
| if (!lua_checkstack(stream->hlua->T, 1)) { |
| SEND_ERR(stream->be, "Lua converter '%s': full stack.\n", fcn->name); |
| RESET_SAFE_LJMP(stream->hlua); |
| return 0; |
| } |
| hlua_arg2lua(stream->hlua->T, arg_p); |
| stream->hlua->nargs++; |
| } |
| } |
| |
| /* We must initialize the execution timeouts. */ |
| hlua_timer_init(&stream->hlua->timer, hlua_timeout_session); |
| |
| /* At this point the execution is safe. */ |
| RESET_SAFE_LJMP(stream->hlua); |
| } |
| |
| /* Execute the function. */ |
| switch (hlua_ctx_resume(stream->hlua, 0)) { |
| /* finished. */ |
| case HLUA_E_OK: |
| /* If the stack is empty, the function fails. */ |
| if (lua_gettop(stream->hlua->T) <= 0) |
| return 0; |
| |
| /* Convert the returned value in sample. */ |
| hlua_lua2smp(stream->hlua->T, -1, smp); |
| lua_pop(stream->hlua->T, 1); |
| return 1; |
| |
| /* yield. */ |
| case HLUA_E_AGAIN: |
| SEND_ERR(stream->be, "Lua converter '%s': cannot use yielded functions.\n", fcn->name); |
| return 0; |
| |
| /* finished with error. */ |
| case HLUA_E_ERRMSG: |
| /* Display log. */ |
| SEND_ERR(stream->be, "Lua converter '%s': %s.\n", |
| fcn->name, lua_tostring(stream->hlua->T, -1)); |
| lua_pop(stream->hlua->T, 1); |
| return 0; |
| |
| case HLUA_E_ETMOUT: |
| SEND_ERR(stream->be, "Lua converter '%s': execution timeout.\n", fcn->name); |
| return 0; |
| |
| case HLUA_E_NOMEM: |
| SEND_ERR(stream->be, "Lua converter '%s': out of memory error.\n", fcn->name); |
| return 0; |
| |
| case HLUA_E_YIELD: |
| SEND_ERR(stream->be, "Lua converter '%s': yield functions like core.tcp() or core.sleep() are not allowed.\n", fcn->name); |
| return 0; |
| |
| case HLUA_E_ERR: |
| /* Display log. */ |
| SEND_ERR(stream->be, "Lua converter '%s' returns an unknown error.\n", fcn->name); |
| __fallthrough; |
| |
| default: |
| return 0; |
| } |
| } |
| |
| /* Wrapper called by HAProxy to execute a sample-fetch. this wrapper |
| * doesn't allow "yield" functions because the HAProxy engine cannot |
| * resume sample-fetches. This function will be called by the sample |
| * fetch engine to call lua-based fetch operations. |
| */ |
| static int hlua_sample_fetch_wrapper(const struct arg *arg_p, struct sample *smp, |
| const char *kw, void *private) |
| { |
| struct hlua_function *fcn = private; |
| struct stream *stream = smp->strm; |
| const char *error; |
| unsigned int hflags = HLUA_TXN_NOTERM | HLUA_TXN_SMP_CTX; |
| |
| if (!stream) |
| return 0; |
| |
| /* In the execution wrappers linked with a stream, the |
| * Lua context can be not initialized. This behavior |
| * permits to save performances because a systematic |
| * Lua initialization cause 5% performances loss. |
| */ |
| if (!stream->hlua) { |
| struct hlua *hlua; |
| |
| hlua = pool_alloc(pool_head_hlua); |
| if (!hlua) { |
| SEND_ERR(stream->be, "Lua sample-fetch '%s': can't initialize Lua context.\n", fcn->name); |
| return 0; |
| } |
| hlua->T = NULL; |
| stream->hlua = hlua; |
| if (!hlua_ctx_init(stream->hlua, fcn_ref_to_stack_id(fcn), stream->task)) { |
| SEND_ERR(stream->be, "Lua sample-fetch '%s': can't initialize Lua context.\n", fcn->name); |
| return 0; |
| } |
| } |
| |
| /* If it is the first run, initialize the data for the call. */ |
| if (!HLUA_IS_RUNNING(stream->hlua)) { |
| |
| /* The following Lua calls can fail. */ |
| if (!SET_SAFE_LJMP(stream->hlua)) { |
| if (lua_type(stream->hlua->T, -1) == LUA_TSTRING) |
| error = lua_tostring(stream->hlua->T, -1); |
| else |
| error = "critical error"; |
| SEND_ERR(smp->px, "Lua sample-fetch '%s': %s.\n", fcn->name, error); |
| return 0; |
| } |
| |
| /* Check stack available size. */ |
| if (!lua_checkstack(stream->hlua->T, 2)) { |
| SEND_ERR(smp->px, "Lua sample-fetch '%s': full stack.\n", fcn->name); |
| RESET_SAFE_LJMP(stream->hlua); |
| return 0; |
| } |
| |
| /* Restore the function in the stack. */ |
| hlua_pushref(stream->hlua->T, fcn->function_ref[stream->hlua->state_id]); |
| |
| /* push arguments in the stack. */ |
| if (!hlua_txn_new(stream->hlua->T, stream, smp->px, smp->opt & SMP_OPT_DIR, hflags)) { |
| SEND_ERR(smp->px, "Lua sample-fetch '%s': full stack.\n", fcn->name); |
| RESET_SAFE_LJMP(stream->hlua); |
| return 0; |
| } |
| stream->hlua->nargs = 1; |
| |
| /* push keywords in the stack. */ |
| for (; arg_p && arg_p->type != ARGT_STOP; arg_p++) { |
| /* Check stack available size. */ |
| if (!lua_checkstack(stream->hlua->T, 1)) { |
| SEND_ERR(smp->px, "Lua sample-fetch '%s': full stack.\n", fcn->name); |
| RESET_SAFE_LJMP(stream->hlua); |
| return 0; |
| } |
| hlua_arg2lua(stream->hlua->T, arg_p); |
| stream->hlua->nargs++; |
| } |
| |
| /* We must initialize the execution timeouts. */ |
| hlua_timer_init(&stream->hlua->timer, hlua_timeout_session); |
| |
| /* At this point the execution is safe. */ |
| RESET_SAFE_LJMP(stream->hlua); |
| } |
| |
| /* Execute the function. */ |
| switch (hlua_ctx_resume(stream->hlua, 0)) { |
| /* finished. */ |
| case HLUA_E_OK: |
| /* If the stack is empty, the function fails. */ |
| if (lua_gettop(stream->hlua->T) <= 0) |
| return 0; |
| |
| /* Convert the returned value in sample. */ |
| hlua_lua2smp(stream->hlua->T, -1, smp); |
| lua_pop(stream->hlua->T, 1); |
| |
| /* Set the end of execution flag. */ |
| smp->flags &= ~SMP_F_MAY_CHANGE; |
| return 1; |
| |
| /* yield. */ |
| case HLUA_E_AGAIN: |
| SEND_ERR(smp->px, "Lua sample-fetch '%s': cannot use yielded functions.\n", fcn->name); |
| return 0; |
| |
| /* finished with error. */ |
| case HLUA_E_ERRMSG: |
| /* Display log. */ |
| SEND_ERR(smp->px, "Lua sample-fetch '%s': %s.\n", |
| fcn->name, lua_tostring(stream->hlua->T, -1)); |
| lua_pop(stream->hlua->T, 1); |
| return 0; |
| |
| case HLUA_E_ETMOUT: |
| SEND_ERR(smp->px, "Lua sample-fetch '%s': execution timeout.\n", fcn->name); |
| return 0; |
| |
| case HLUA_E_NOMEM: |
| SEND_ERR(smp->px, "Lua sample-fetch '%s': out of memory error.\n", fcn->name); |
| return 0; |
| |
| case HLUA_E_YIELD: |
| SEND_ERR(smp->px, "Lua sample-fetch '%s': yield not allowed.\n", fcn->name); |
| return 0; |
| |
| case HLUA_E_ERR: |
| /* Display log. */ |
| SEND_ERR(smp->px, "Lua sample-fetch '%s' returns an unknown error.\n", fcn->name); |
| __fallthrough; |
| |
| default: |
| return 0; |
| } |
| } |
| |
| /* This function is an LUA binding used for registering |
| * "sample-conv" functions. It expects a converter name used |
| * in the haproxy configuration file, and an LUA function. |
| */ |
| __LJMP static int hlua_register_converters(lua_State *L) |
| { |
| struct sample_conv_kw_list *sck; |
| const char *name; |
| int ref; |
| int len; |
| struct hlua_function *fcn = NULL; |
| struct sample_conv *sc; |
| struct buffer *trash; |
| |
| MAY_LJMP(check_args(L, 2, "register_converters")); |
| |
| if (hlua_gethlua(L)) { |
| /* runtime processing */ |
| WILL_LJMP(luaL_error(L, "register_converters: not available outside of body context")); |
| } |
| |
| /* First argument : converter name. */ |
| name = MAY_LJMP(luaL_checkstring(L, 1)); |
| |
| /* Second argument : lua function. */ |
| ref = MAY_LJMP(hlua_checkfunction(L, 2)); |
| |
| /* Check if the converter is already registered */ |
| trash = get_trash_chunk(); |
| chunk_printf(trash, "lua.%s", name); |
| sc = find_sample_conv(trash->area, trash->data); |
| if (sc != NULL) { |
| fcn = sc->private; |
| if (fcn->function_ref[hlua_state_id] != -1) { |
| ha_warning("Trying to register converter 'lua.%s' more than once. " |
| "This will become a hard error in version 2.5.\n", name); |
| hlua_unref(L, fcn->function_ref[hlua_state_id]); |
| } |
| fcn->function_ref[hlua_state_id] = ref; |
| return 0; |
| } |
| |
| /* Allocate and fill the sample fetch keyword struct. */ |
| sck = calloc(1, sizeof(*sck) + sizeof(struct sample_conv) * 2); |
| if (!sck) |
| goto alloc_error; |
| fcn = new_hlua_function(); |
| if (!fcn) |
| goto alloc_error; |
| |
| /* Fill fcn. */ |
| fcn->name = strdup(name); |
| if (!fcn->name) |
| goto alloc_error; |
| fcn->function_ref[hlua_state_id] = ref; |
| |
| /* List head */ |
| sck->list.n = sck->list.p = NULL; |
| |
| /* converter keyword. */ |
| len = strlen("lua.") + strlen(name) + 1; |
| sck->kw[0].kw = calloc(1, len); |
| if (!sck->kw[0].kw) |
| goto alloc_error; |
| |
| snprintf((char *)sck->kw[0].kw, len, "lua.%s", name); |
| sck->kw[0].process = hlua_sample_conv_wrapper; |
| sck->kw[0].arg_mask = ARG12(0,STR,STR,STR,STR,STR,STR,STR,STR,STR,STR,STR,STR); |
| sck->kw[0].val_args = NULL; |
| sck->kw[0].in_type = SMP_T_STR; |
| sck->kw[0].out_type = SMP_T_STR; |
| sck->kw[0].private = fcn; |
| |
| /* Register this new converter */ |
| sample_register_convs(sck); |
| |
| return 0; |
| |
| alloc_error: |
| release_hlua_function(fcn); |
| hlua_unref(L, ref); |
| ha_free(&sck); |
| WILL_LJMP(luaL_error(L, "Lua out of memory error.")); |
| return 0; /* Never reached */ |
| } |
| |
| /* This function is an LUA binding used for registering |
| * "sample-fetch" functions. It expects a converter name used |
| * in the haproxy configuration file, and an LUA function. |
| */ |
| __LJMP static int hlua_register_fetches(lua_State *L) |
| { |
| const char *name; |
| int ref; |
| int len; |
| struct sample_fetch_kw_list *sfk; |
| struct hlua_function *fcn = NULL; |
| struct sample_fetch *sf; |
| struct buffer *trash; |
| |
| MAY_LJMP(check_args(L, 2, "register_fetches")); |
| |
| if (hlua_gethlua(L)) { |
| /* runtime processing */ |
| WILL_LJMP(luaL_error(L, "register_fetches: not available outside of body context")); |
| } |
| |
| /* First argument : sample-fetch name. */ |
| name = MAY_LJMP(luaL_checkstring(L, 1)); |
| |
| /* Second argument : lua function. */ |
| ref = MAY_LJMP(hlua_checkfunction(L, 2)); |
| |
| /* Check if the sample-fetch is already registered */ |
| trash = get_trash_chunk(); |
| chunk_printf(trash, "lua.%s", name); |
| sf = find_sample_fetch(trash->area, trash->data); |
| if (sf != NULL) { |
| fcn = sf->private; |
| if (fcn->function_ref[hlua_state_id] != -1) { |
| ha_warning("Trying to register sample-fetch 'lua.%s' more than once. " |
| "This will become a hard error in version 2.5.\n", name); |
| hlua_unref(L, fcn->function_ref[hlua_state_id]); |
| } |
| fcn->function_ref[hlua_state_id] = ref; |
| return 0; |
| } |
| |
| /* Allocate and fill the sample fetch keyword struct. */ |
| sfk = calloc(1, sizeof(*sfk) + sizeof(struct sample_fetch) * 2); |
| if (!sfk) |
| goto alloc_error; |
| fcn = new_hlua_function(); |
| if (!fcn) |
| goto alloc_error; |
| |
| /* Fill fcn. */ |
| fcn->name = strdup(name); |
| if (!fcn->name) |
| goto alloc_error; |
| fcn->function_ref[hlua_state_id] = ref; |
| |
| /* List head */ |
| sfk->list.n = sfk->list.p = NULL; |
| |
| /* sample-fetch keyword. */ |
| len = strlen("lua.") + strlen(name) + 1; |
| sfk->kw[0].kw = calloc(1, len); |
| if (!sfk->kw[0].kw) |
| goto alloc_error; |
| |
| snprintf((char *)sfk->kw[0].kw, len, "lua.%s", name); |
| sfk->kw[0].process = hlua_sample_fetch_wrapper; |
| sfk->kw[0].arg_mask = ARG12(0,STR,STR,STR,STR,STR,STR,STR,STR,STR,STR,STR,STR); |
| sfk->kw[0].val_args = NULL; |
| sfk->kw[0].out_type = SMP_T_STR; |
| sfk->kw[0].use = SMP_USE_HTTP_ANY; |
| sfk->kw[0].val = 0; |
| sfk->kw[0].private = fcn; |
| |
| /* Register this new fetch. */ |
| sample_register_fetches(sfk); |
| |
| return 0; |
| |
| alloc_error: |
| release_hlua_function(fcn); |
| hlua_unref(L, ref); |
| ha_free(&sfk); |
| WILL_LJMP(luaL_error(L, "Lua out of memory error.")); |
| return 0; /* Never reached */ |
| } |
| |
| /* This function is a lua binding to set the wake_time. |
| */ |
| __LJMP static int hlua_set_wake_time(lua_State *L) |
| { |
| struct hlua *hlua; |
| unsigned int delay; |
| int wakeup_ms; // tick value |
| |
| /* Get hlua struct, or NULL if we execute from main lua state */ |
| hlua = hlua_gethlua(L); |
| if (!hlua) { |
| return 0; |
| } |
| |
| MAY_LJMP(check_args(L, 1, "wake_time")); |
| |
| delay = MAY_LJMP(luaL_checkinteger(L, 1)); |
| wakeup_ms = tick_add(now_ms, delay); |
| hlua->wake_time = wakeup_ms; |
| return 0; |
| } |
| |
| /* This function is a wrapper to execute each LUA function declared as an action |
| * wrapper during the initialisation period. This function may return any |
| * ACT_RET_* value. On error ACT_RET_CONT is returned and the action is |
| * ignored. If the lua action yields, ACT_RET_YIELD is returned. On success, the |
| * return value is the first element on the stack. |
| */ |
| static enum act_return hlua_action(struct act_rule *rule, struct proxy *px, |
| struct session *sess, struct stream *s, int flags) |
| { |
| char **arg; |
| unsigned int hflags = HLUA_TXN_ACT_CTX; |
| int dir, act_ret = ACT_RET_CONT; |
| const char *error; |
| |
| switch (rule->from) { |
| case ACT_F_TCP_REQ_CNT: dir = SMP_OPT_DIR_REQ; break; |
| case ACT_F_TCP_RES_CNT: dir = SMP_OPT_DIR_RES; break; |
| case ACT_F_HTTP_REQ: dir = SMP_OPT_DIR_REQ; break; |
| case ACT_F_HTTP_RES: dir = SMP_OPT_DIR_RES; break; |
| default: |
| SEND_ERR(px, "Lua: internal error while execute action.\n"); |
| goto end; |
| } |
| |
| /* In the execution wrappers linked with a stream, the |
| * Lua context can be not initialized. This behavior |
| * permits to save performances because a systematic |
| * Lua initialization cause 5% performances loss. |
| */ |
| if (!s->hlua) { |
| struct hlua *hlua; |
| |
| hlua = pool_alloc(pool_head_hlua); |
| if (!hlua) { |
| SEND_ERR(px, "Lua action '%s': can't initialize Lua context.\n", |
| rule->arg.hlua_rule->fcn->name); |
| goto end; |
| } |
| HLUA_INIT(hlua); |
| s->hlua = hlua; |
| if (!hlua_ctx_init(s->hlua, fcn_ref_to_stack_id(rule->arg.hlua_rule->fcn), s->task)) { |
| SEND_ERR(px, "Lua action '%s': can't initialize Lua context.\n", |
| rule->arg.hlua_rule->fcn->name); |
| goto end; |
| } |
| } |
| |
| /* If it is the first run, initialize the data for the call. */ |
| if (!HLUA_IS_RUNNING(s->hlua)) { |
| |
| /* The following Lua calls can fail. */ |
| if (!SET_SAFE_LJMP(s->hlua)) { |
| if (lua_type(s->hlua->T, -1) == LUA_TSTRING) |
| error = lua_tostring(s->hlua->T, -1); |
| else |
| error = "critical error"; |
| SEND_ERR(px, "Lua function '%s': %s.\n", |
| rule->arg.hlua_rule->fcn->name, error); |
| goto end; |
| } |
| |
| /* Check stack available size. */ |
| if (!lua_checkstack(s->hlua->T, 1)) { |
| SEND_ERR(px, "Lua function '%s': full stack.\n", |
| rule->arg.hlua_rule->fcn->name); |
| RESET_SAFE_LJMP(s->hlua); |
| goto end; |
| } |
| |
| /* Restore the function in the stack. */ |
| hlua_pushref(s->hlua->T, rule->arg.hlua_rule->fcn->function_ref[s->hlua->state_id]); |
| |
| /* Create and and push object stream in the stack. */ |
| if (!hlua_txn_new(s->hlua->T, s, px, dir, hflags)) { |
| SEND_ERR(px, "Lua function '%s': full stack.\n", |
| rule->arg.hlua_rule->fcn->name); |
| RESET_SAFE_LJMP(s->hlua); |
| goto end; |
| } |
| s->hlua->nargs = 1; |
| |
| /* push keywords in the stack. */ |
| for (arg = rule->arg.hlua_rule->args; arg && *arg; arg++) { |
| if (!lua_checkstack(s->hlua->T, 1)) { |
| SEND_ERR(px, "Lua function '%s': full stack.\n", |
| rule->arg.hlua_rule->fcn->name); |
| RESET_SAFE_LJMP(s->hlua); |
| goto end; |
| } |
| lua_pushstring(s->hlua->T, *arg); |
| s->hlua->nargs++; |
| } |
| |
| /* Now the execution is safe. */ |
| RESET_SAFE_LJMP(s->hlua); |
| |
| /* We must initialize the execution timeouts. */ |
| hlua_timer_init(&s->hlua->timer, hlua_timeout_session); |
| } |
| |
| /* Execute the function. */ |
| switch (hlua_ctx_resume(s->hlua, !(flags & ACT_OPT_FINAL))) { |
| /* finished. */ |
| case HLUA_E_OK: |
| /* Catch the return value */ |
| if (lua_gettop(s->hlua->T) > 0) |
| act_ret = lua_tointeger(s->hlua->T, -1); |
| |
| /* Set timeout in the required channel. */ |
| if (act_ret == ACT_RET_YIELD) { |
| if (flags & ACT_OPT_FINAL) |
| goto err_yield; |
| |
| if (dir == SMP_OPT_DIR_REQ) |
| s->req.analyse_exp = tick_first((tick_is_expired(s->req.analyse_exp, now_ms) ? 0 : s->req.analyse_exp), |
| s->hlua->wake_time); |
| else |
| s->res.analyse_exp = tick_first((tick_is_expired(s->res.analyse_exp, now_ms) ? 0 : s->res.analyse_exp), |
| s->hlua->wake_time); |
| } |
| goto end; |
| |
| /* yield. */ |
| case HLUA_E_AGAIN: |
| /* Set timeout in the required channel. */ |
| if (dir == SMP_OPT_DIR_REQ) |
| s->req.analyse_exp = tick_first((tick_is_expired(s->req.analyse_exp, now_ms) ? 0 : s->req.analyse_exp), |
| s->hlua->wake_time); |
| else |
| s->res.analyse_exp = tick_first((tick_is_expired(s->res.analyse_exp, now_ms) ? 0 : s->res.analyse_exp), |
| s->hlua->wake_time); |
| |
| /* Some actions can be wake up when a "write" event |
| * is detected on a response channel. This is useful |
| * only for actions targeted on the requests. |
| */ |
| if (HLUA_IS_WAKERESWR(s->hlua)) |
| s->res.flags |= CF_WAKE_WRITE; |
| if (HLUA_IS_WAKEREQWR(s->hlua)) |
| s->req.flags |= CF_WAKE_WRITE; |
| act_ret = ACT_RET_YIELD; |
| goto end; |
| |
| /* finished with error. */ |
| case HLUA_E_ERRMSG: |
| /* Display log. */ |
| SEND_ERR(px, "Lua function '%s': %s.\n", |
| rule->arg.hlua_rule->fcn->name, lua_tostring(s->hlua->T, -1)); |
| lua_pop(s->hlua->T, 1); |
| goto end; |
| |
| case HLUA_E_ETMOUT: |
| SEND_ERR(px, "Lua function '%s': execution timeout.\n", rule->arg.hlua_rule->fcn->name); |
| goto end; |
| |
| case HLUA_E_NOMEM: |
| SEND_ERR(px, "Lua function '%s': out of memory error.\n", rule->arg.hlua_rule->fcn->name); |
| goto end; |
| |
| case HLUA_E_YIELD: |
| err_yield: |
| act_ret = ACT_RET_CONT; |
| SEND_ERR(px, "Lua function '%s': aborting Lua processing on expired timeout.\n", |
| rule->arg.hlua_rule->fcn->name); |
| goto end; |
| |
| case HLUA_E_ERR: |
| /* Display log. */ |
| SEND_ERR(px, "Lua function '%s' return an unknown error.\n", |
| rule->arg.hlua_rule->fcn->name); |
| |
| default: |
| goto end; |
| } |
| |
| end: |
| if (act_ret != ACT_RET_YIELD && s->hlua) |
| s->hlua->wake_time = TICK_ETERNITY; |
| return act_ret; |
| } |
| |
| struct task *hlua_applet_wakeup(struct task *t, void *context, unsigned int state) |
| { |
| struct appctx *ctx = context; |
| |
| appctx_wakeup(ctx); |
| t->expire = TICK_ETERNITY; |
| return t; |
| } |
| |
| static int hlua_applet_tcp_init(struct appctx *ctx) |
| { |
| struct hlua_tcp_ctx *tcp_ctx = applet_reserve_svcctx(ctx, sizeof(*tcp_ctx)); |
| struct stconn *sc = appctx_sc(ctx); |
| struct stream *strm = __sc_strm(sc); |
| struct hlua *hlua; |
| struct task *task; |
| char **arg; |
| const char *error; |
| |
| hlua = pool_alloc(pool_head_hlua); |
| if (!hlua) { |
| SEND_ERR(strm->be, "Lua applet tcp '%s': out of memory.\n", |
| ctx->rule->arg.hlua_rule->fcn->name); |
| return -1; |
| } |
| HLUA_INIT(hlua); |
| tcp_ctx->hlua = hlua; |
| tcp_ctx->flags = 0; |
| |
| /* Create task used by signal to wakeup applets. */ |
| task = task_new_here(); |
| if (!task) { |
| SEND_ERR(strm->be, "Lua applet tcp '%s': out of memory.\n", |
| ctx->rule->arg.hlua_rule->fcn->name); |
| return -1; |
| } |
| task->nice = 0; |
| task->context = ctx; |
| task->process = hlua_applet_wakeup; |
| tcp_ctx->task = task; |
| |
| /* In the execution wrappers linked with a stream, the |
| * Lua context can be not initialized. This behavior |
| * permits to save performances because a systematic |
| * Lua initialization cause 5% performances loss. |
| */ |
| if (!hlua_ctx_init(hlua, fcn_ref_to_stack_id(ctx->rule->arg.hlua_rule->fcn), task)) { |
| SEND_ERR(strm->be, "Lua applet tcp '%s': can't initialize Lua context.\n", |
| ctx->rule->arg.hlua_rule->fcn->name); |
| return -1; |
| } |
| |
| /* Set timeout according with the applet configuration. */ |
| hlua_timer_init(&hlua->timer, ctx->applet->timeout); |
| |
| /* The following Lua calls can fail. */ |
| if (!SET_SAFE_LJMP(hlua)) { |
| if (lua_type(hlua->T, -1) == LUA_TSTRING) |
| error = lua_tostring(hlua->T, -1); |
| else |
| error = "critical error"; |
| SEND_ERR(strm->be, "Lua applet tcp '%s': %s.\n", |
| ctx->rule->arg.hlua_rule->fcn->name, error); |
| return -1; |
| } |
| |
| /* Check stack available size. */ |
| if (!lua_checkstack(hlua->T, 1)) { |
| SEND_ERR(strm->be, "Lua applet tcp '%s': full stack.\n", |
| ctx->rule->arg.hlua_rule->fcn->name); |
| RESET_SAFE_LJMP(hlua); |
| return -1; |
| } |
| |
| /* Restore the function in the stack. */ |
| hlua_pushref(hlua->T, ctx->rule->arg.hlua_rule->fcn->function_ref[hlua->state_id]); |
| |
| /* Create and and push object stream in the stack. */ |
| if (!hlua_applet_tcp_new(hlua->T, ctx)) { |
| SEND_ERR(strm->be, "Lua applet tcp '%s': full stack.\n", |
| ctx->rule->arg.hlua_rule->fcn->name); |
| RESET_SAFE_LJMP(hlua); |
| return -1; |
| } |
| hlua->nargs = 1; |
| |
| /* push keywords in the stack. */ |
| for (arg = ctx->rule->arg.hlua_rule->args; arg && *arg; arg++) { |
| if (!lua_checkstack(hlua->T, 1)) { |
| SEND_ERR(strm->be, "Lua applet tcp '%s': full stack.\n", |
| ctx->rule->arg.hlua_rule->fcn->name); |
| RESET_SAFE_LJMP(hlua); |
| return -1; |
| } |
| lua_pushstring(hlua->T, *arg); |
| hlua->nargs++; |
| } |
| |
| RESET_SAFE_LJMP(hlua); |
| |
| /* Wakeup the applet ASAP. */ |
| applet_need_more_data(ctx); |
| applet_have_more_data(ctx); |
| |
| return 0; |
| } |
| |
| void hlua_applet_tcp_fct(struct appctx *ctx) |
| { |
| struct hlua_tcp_ctx *tcp_ctx = ctx->svcctx; |
| struct stconn *sc = appctx_sc(ctx); |
| struct stream *strm = __sc_strm(sc); |
| struct act_rule *rule = ctx->rule; |
| struct proxy *px = strm->be; |
| struct hlua *hlua = tcp_ctx->hlua; |
| |
| if (unlikely(se_fl_test(ctx->sedesc, (SE_FL_EOS|SE_FL_ERROR|SE_FL_SHR|SE_FL_SHW)))) |
| goto out; |
| |
| /* The applet execution is already done. */ |
| if (tcp_ctx->flags & APPLET_DONE) |
| goto out; |
| |
| /* Execute the function. */ |
| switch (hlua_ctx_resume(hlua, 1)) { |
| /* finished. */ |
| case HLUA_E_OK: |
| tcp_ctx->flags |= APPLET_DONE; |
| se_fl_set(ctx->sedesc, SE_FL_EOI|SE_FL_EOS); |
| break; |
| |
| /* yield. */ |
| case HLUA_E_AGAIN: |
| if (hlua->wake_time != TICK_ETERNITY) |
| task_schedule(tcp_ctx->task, hlua->wake_time); |
| break; |
| |
| /* finished with error. */ |
| case HLUA_E_ERRMSG: |
| /* Display log. */ |
| SEND_ERR(px, "Lua applet tcp '%s': %s.\n", |
| rule->arg.hlua_rule->fcn->name, lua_tostring(hlua->T, -1)); |
| lua_pop(hlua->T, 1); |
| goto error; |
| |
| case HLUA_E_ETMOUT: |
| SEND_ERR(px, "Lua applet tcp '%s': execution timeout.\n", |
| rule->arg.hlua_rule->fcn->name); |
| goto error; |
| |
| case HLUA_E_NOMEM: |
| SEND_ERR(px, "Lua applet tcp '%s': out of memory error.\n", |
| rule->arg.hlua_rule->fcn->name); |
| goto error; |
| |
| case HLUA_E_YIELD: /* unexpected */ |
| SEND_ERR(px, "Lua applet tcp '%s': yield not allowed.\n", |
| rule->arg.hlua_rule->fcn->name); |
| goto error; |
| |
| case HLUA_E_ERR: |
| /* Display log. */ |
| SEND_ERR(px, "Lua applet tcp '%s' return an unknown error.\n", |
| rule->arg.hlua_rule->fcn->name); |
| goto error; |
| |
| default: |
| goto error; |
| } |
| |
| out: |
| /* eat the whole request */ |
| co_skip(sc_oc(sc), co_data(sc_oc(sc))); |
| return; |
| |
| error: |
| se_fl_set(ctx->sedesc, SE_FL_ERROR); |
| tcp_ctx->flags |= APPLET_DONE; |
| goto out; |
| } |
| |
| static void hlua_applet_tcp_release(struct appctx *ctx) |
| { |
| struct hlua_tcp_ctx *tcp_ctx = ctx->svcctx; |
| |
| task_destroy(tcp_ctx->task); |
| tcp_ctx->task = NULL; |
| hlua_ctx_destroy(tcp_ctx->hlua); |
| tcp_ctx->hlua = NULL; |
| } |
| |
| /* The function returns 0 if the initialisation is complete or -1 if |
| * an errors occurs. It also reserves the appctx for an hlua_http_ctx. |
| */ |
| static int hlua_applet_http_init(struct appctx *ctx) |
| { |
| struct hlua_http_ctx *http_ctx = applet_reserve_svcctx(ctx, sizeof(*http_ctx)); |
| struct stconn *sc = appctx_sc(ctx); |
| struct stream *strm = __sc_strm(sc); |
| struct http_txn *txn; |
| struct hlua *hlua; |
| char **arg; |
| struct task *task; |
| const char *error; |
| |
| txn = strm->txn; |
| hlua = pool_alloc(pool_head_hlua); |
| if (!hlua) { |
| SEND_ERR(strm->be, "Lua applet http '%s': out of memory.\n", |
| ctx->rule->arg.hlua_rule->fcn->name); |
| return -1; |
| } |
| HLUA_INIT(hlua); |
| http_ctx->hlua = hlua; |
| http_ctx->left_bytes = -1; |
| http_ctx->flags = 0; |
| |
| if (txn->req.flags & HTTP_MSGF_VER_11) |
| http_ctx->flags |= APPLET_HTTP11; |
| |
| /* Create task used by signal to wakeup applets. */ |
| task = task_new_here(); |
| if (!task) { |
| SEND_ERR(strm->be, "Lua applet http '%s': out of memory.\n", |
| ctx->rule->arg.hlua_rule->fcn->name); |
| return -1; |
| } |
| task->nice = 0; |
| task->context = ctx; |
| task->process = hlua_applet_wakeup; |
| http_ctx->task = task; |
| |
| /* In the execution wrappers linked with a stream, the |
| * Lua context can be not initialized. This behavior |
| * permits to save performances because a systematic |
| * Lua initialization cause 5% performances loss. |
| */ |
| if (!hlua_ctx_init(hlua, fcn_ref_to_stack_id(ctx->rule->arg.hlua_rule->fcn), task)) { |
| SEND_ERR(strm->be, "Lua applet http '%s': can't initialize Lua context.\n", |
| ctx->rule->arg.hlua_rule->fcn->name); |
| return -1; |
| } |
| |
| /* Set timeout according with the applet configuration. */ |
| hlua_timer_init(&hlua->timer, ctx->applet->timeout); |
| |
| /* The following Lua calls can fail. */ |
| if (!SET_SAFE_LJMP(hlua)) { |
| if (lua_type(hlua->T, -1) == LUA_TSTRING) |
| error = lua_tostring(hlua->T, -1); |
| else |
| error = "critical error"; |
| SEND_ERR(strm->be, "Lua applet http '%s': %s.\n", |
| ctx->rule->arg.hlua_rule->fcn->name, error); |
| return -1; |
| } |
| |
| /* Check stack available size. */ |
| if (!lua_checkstack(hlua->T, 1)) { |
| SEND_ERR(strm->be, "Lua applet http '%s': full stack.\n", |
| ctx->rule->arg.hlua_rule->fcn->name); |
| RESET_SAFE_LJMP(hlua); |
| return -1; |
| } |
| |
| /* Restore the function in the stack. */ |
| hlua_pushref(hlua->T, ctx->rule->arg.hlua_rule->fcn->function_ref[hlua->state_id]); |
| |
| /* Create and and push object stream in the stack. */ |
| if (!hlua_applet_http_new(hlua->T, ctx)) { |
| SEND_ERR(strm->be, "Lua applet http '%s': full stack.\n", |
| ctx->rule->arg.hlua_rule->fcn->name); |
| RESET_SAFE_LJMP(hlua); |
| return -1; |
| } |
| hlua->nargs = 1; |
| |
| /* push keywords in the stack. */ |
| for (arg = ctx->rule->arg.hlua_rule->args; arg && *arg; arg++) { |
| if (!lua_checkstack(hlua->T, 1)) { |
| SEND_ERR(strm->be, "Lua applet http '%s': full stack.\n", |
| ctx->rule->arg.hlua_rule->fcn->name); |
| RESET_SAFE_LJMP(hlua); |
| return -1; |
| } |
| lua_pushstring(hlua->T, *arg); |
| hlua->nargs++; |
| } |
| |
| RESET_SAFE_LJMP(hlua); |
| |
| /* Wakeup the applet when data is ready for read. */ |
| applet_need_more_data(ctx); |
| |
| return 0; |
| } |
| |
| void hlua_applet_http_fct(struct appctx *ctx) |
| { |
| struct hlua_http_ctx *http_ctx = ctx->svcctx; |
| struct stconn *sc = appctx_sc(ctx); |
| struct stream *strm = __sc_strm(sc); |
| struct channel *req = sc_oc(sc); |
| struct channel *res = sc_ic(sc); |
| struct act_rule *rule = ctx->rule; |
| struct proxy *px = strm->be; |
| struct hlua *hlua = http_ctx->hlua; |
| struct htx *req_htx, *res_htx; |
| |
| res_htx = htx_from_buf(&res->buf); |
| |
| if (unlikely(se_fl_test(ctx->sedesc, (SE_FL_EOS|SE_FL_ERROR|SE_FL_SHR|SE_FL_SHW)))) |
| goto out; |
| |
| /* The applet execution is already done. */ |
| if (http_ctx->flags & APPLET_DONE) |
| goto out; |
| |
| /* Check if the input buffer is available. */ |
| if (!b_size(&res->buf)) { |
| sc_need_room(sc, 0); |
| goto out; |
| } |
| |
| /* Set the currently running flag. */ |
| if (!HLUA_IS_RUNNING(hlua) && |
| !(http_ctx->flags & APPLET_DONE)) { |
| if (!co_data(req)) { |
| applet_need_more_data(ctx); |
| goto out; |
| } |
| } |
| |
| /* Execute the function. */ |
| switch (hlua_ctx_resume(hlua, 1)) { |
| /* finished. */ |
| case HLUA_E_OK: |
| http_ctx->flags |= APPLET_DONE; |
| break; |
| |
| /* yield. */ |
| case HLUA_E_AGAIN: |
| if (hlua->wake_time != TICK_ETERNITY) |
| task_schedule(http_ctx->task, hlua->wake_time); |
| goto out; |
| |
| /* finished with error. */ |
| case HLUA_E_ERRMSG: |
| /* Display log. */ |
| SEND_ERR(px, "Lua applet http '%s': %s.\n", |
| rule->arg.hlua_rule->fcn->name, lua_tostring(hlua->T, -1)); |
| lua_pop(hlua->T, 1); |
| goto error; |
| |
| case HLUA_E_ETMOUT: |
| SEND_ERR(px, "Lua applet http '%s': execution timeout.\n", |
| rule->arg.hlua_rule->fcn->name); |
| goto error; |
| |
| case HLUA_E_NOMEM: |
| SEND_ERR(px, "Lua applet http '%s': out of memory error.\n", |
| rule->arg.hlua_rule->fcn->name); |
| goto error; |
| |
| case HLUA_E_YIELD: /* unexpected */ |
| SEND_ERR(px, "Lua applet http '%s': yield not allowed.\n", |
| rule->arg.hlua_rule->fcn->name); |
| goto error; |
| |
| case HLUA_E_ERR: |
| /* Display log. */ |
| SEND_ERR(px, "Lua applet http '%s' return an unknown error.\n", |
| rule->arg.hlua_rule->fcn->name); |
| goto error; |
| |
| default: |
| goto error; |
| } |
| |
| if (http_ctx->flags & APPLET_DONE) { |
| if (http_ctx->flags & APPLET_RSP_SENT) |
| goto out; |
| |
| if (!(http_ctx->flags & APPLET_HDR_SENT)) |
| goto error; |
| |
| /* no more data are expected. If the response buffer is empty |
| * for a chunked message, be sure to add something (EOT block in |
| * this case) to have something to send. It is important to be |
| * sure the EOM flags will be handled by the endpoint. |
| */ |
| if (htx_is_empty(res_htx) && (strm->txn->rsp.flags & (HTTP_MSGF_XFER_LEN|HTTP_MSGF_CNT_LEN)) == HTTP_MSGF_XFER_LEN) { |
| if (!htx_add_endof(res_htx, HTX_BLK_EOT)) { |
| sc_need_room(sc, sizeof(struct htx_blk)+1); |
| goto out; |
| } |
| channel_add_input(res, 1); |
| } |
| |
| res_htx->flags |= HTX_FL_EOM; |
| se_fl_set(ctx->sedesc, SE_FL_EOI|SE_FL_EOS); |
| strm->txn->status = http_ctx->status; |
| http_ctx->flags |= APPLET_RSP_SENT; |
| } |
| |
| out: |
| htx_to_buf(res_htx, &res->buf); |
| /* eat the whole request */ |
| if (co_data(req)) { |
| req_htx = htx_from_buf(&req->buf); |
| co_htx_skip(req, req_htx, co_data(req)); |
| htx_to_buf(req_htx, &req->buf); |
| } |
| return; |
| |
| error: |
| |
| /* If we are in HTTP mode, and we are not send any |
| * data, return a 500 server error in best effort: |
| * if there is no room available in the buffer, |
| * just close the connection. |
| */ |
| if (!(http_ctx->flags & APPLET_HDR_SENT)) { |
| struct buffer *err = &http_err_chunks[HTTP_ERR_500]; |
| |
| channel_erase(res); |
| res->buf.data = b_data(err); |
| memcpy(res->buf.area, b_head(err), b_data(err)); |
| res_htx = htx_from_buf(&res->buf); |
| channel_add_input(res, res_htx->data); |
| se_fl_set(ctx->sedesc, SE_FL_EOI|SE_FL_EOS); |
| } |
| else |
| se_fl_set(ctx->sedesc, SE_FL_ERROR); |
| |
| if (!(strm->flags & SF_ERR_MASK)) |
| strm->flags |= SF_ERR_RESOURCE; |
| http_ctx->flags |= APPLET_DONE; |
| goto out; |
| } |
| |
| static void hlua_applet_http_release(struct appctx *ctx) |
| { |
| struct hlua_http_ctx *http_ctx = ctx->svcctx; |
| |
| task_destroy(http_ctx->task); |
| http_ctx->task = NULL; |
| hlua_ctx_destroy(http_ctx->hlua); |
| http_ctx->hlua = NULL; |
| } |
| |
| /* global {tcp|http}-request parser. Return ACT_RET_PRS_OK in |
| * success case, else return ACT_RET_PRS_ERR. |
| * |
| * This function can fail with an abort() due to an Lua critical error. |
| * We are in the configuration parsing process of HAProxy, this abort() is |
| * tolerated. |
| */ |
| static enum act_parse_ret action_register_lua(const char **args, int *cur_arg, struct proxy *px, |
| struct act_rule *rule, char **err) |
| { |
| struct hlua_function *fcn = rule->kw->private; |
| int i; |
| |
| /* Memory for the rule. */ |
| rule->arg.hlua_rule = calloc(1, sizeof(*rule->arg.hlua_rule)); |
| if (!rule->arg.hlua_rule) { |
| memprintf(err, "out of memory error"); |
| goto error; |
| } |
| |
| /* Memory for arguments. */ |
| rule->arg.hlua_rule->args = calloc(fcn->nargs + 1, |
| sizeof(*rule->arg.hlua_rule->args)); |
| if (!rule->arg.hlua_rule->args) { |
| memprintf(err, "out of memory error"); |
| goto error; |
| } |
| |
| /* Reference the Lua function and store the reference. */ |
| rule->arg.hlua_rule->fcn = fcn; |
| |
| /* Expect some arguments */ |
| for (i = 0; i < fcn->nargs; i++) { |
| if (*args[*cur_arg] == '\0') { |
| memprintf(err, "expect %d arguments", fcn->nargs); |
| goto error; |
| } |
| rule->arg.hlua_rule->args[i] = strdup(args[*cur_arg]); |
| if (!rule->arg.hlua_rule->args[i]) { |
| memprintf(err, "out of memory error"); |
| goto error; |
| } |
| (*cur_arg)++; |
| } |
| rule->arg.hlua_rule->args[i] = NULL; |
| |
| rule->action = ACT_CUSTOM; |
| rule->action_ptr = hlua_action; |
| return ACT_RET_PRS_OK; |
| |
| error: |
| if (rule->arg.hlua_rule) { |
| if (rule->arg.hlua_rule->args) { |
| for (i = 0; i < fcn->nargs; i++) |
| ha_free(&rule->arg.hlua_rule->args[i]); |
| ha_free(&rule->arg.hlua_rule->args); |
| } |
| ha_free(&rule->arg.hlua_rule); |
| } |
| return ACT_RET_PRS_ERR; |
| } |
| |
| static enum act_parse_ret action_register_service_http(const char **args, int *cur_arg, struct proxy *px, |
| struct act_rule *rule, char **err) |
| { |
| struct hlua_function *fcn = rule->kw->private; |
| |
| /* HTTP applets are forbidden in tcp-request rules. |
| * HTTP applet request requires everything initialized by |
| * "http_process_request" (analyzer flag AN_REQ_HTTP_INNER). |
| * The applet will be immediately initialized, but its before |
| * the call of this analyzer. |
| */ |
| if (rule->from != ACT_F_HTTP_REQ) { |
| memprintf(err, "HTTP applets are forbidden from 'tcp-request' rulesets"); |
| return ACT_RET_PRS_ERR; |
| } |
| |
| /* Memory for the rule. */ |
| rule->arg.hlua_rule = calloc(1, sizeof(*rule->arg.hlua_rule)); |
| if (!rule->arg.hlua_rule) { |
| memprintf(err, "out of memory error"); |
| return ACT_RET_PRS_ERR; |
| } |
| |
| /* Reference the Lua function and store the reference. */ |
| rule->arg.hlua_rule->fcn = fcn; |
| |
| /* TODO: later accept arguments. */ |
| rule->arg.hlua_rule->args = NULL; |
| |
| /* Add applet pointer in the rule. */ |
| rule->applet.obj_type = OBJ_TYPE_APPLET; |
| rule->applet.name = fcn->name; |
| rule->applet.init = hlua_applet_http_init; |
| rule->applet.fct = hlua_applet_http_fct; |
| rule->applet.release = hlua_applet_http_release; |
| rule->applet.timeout = hlua_timeout_applet; |
| |
| return ACT_RET_PRS_OK; |
| } |
| |
| /* This function is an LUA binding used for registering |
| * "sample-conv" functions. It expects a converter name used |
| * in the haproxy configuration file, and an LUA function. |
| */ |
| __LJMP static int hlua_register_action(lua_State *L) |
| { |
| struct action_kw_list *akl = NULL; |
| const char *name; |
| int ref; |
| int len; |
| struct hlua_function *fcn = NULL; |
| int nargs; |
| struct buffer *trash; |
| struct action_kw *akw; |
| |
| /* Initialise the number of expected arguments at 0. */ |
| nargs = 0; |
| |
| if (lua_gettop(L) < 3 || lua_gettop(L) > 4) |
| WILL_LJMP(luaL_error(L, "'register_action' needs between 3 and 4 arguments")); |
| |
| if (hlua_gethlua(L)) { |
| /* runtime processing */ |
| WILL_LJMP(luaL_error(L, "register_action: not available outside of body context")); |
| } |
| |
| /* First argument : converter name. */ |
| name = MAY_LJMP(luaL_checkstring(L, 1)); |
| |
| /* Second argument : environment. */ |
| if (lua_type(L, 2) != LUA_TTABLE) |
| WILL_LJMP(luaL_error(L, "register_action: second argument must be a table of strings")); |
| |
| /* Third argument : lua function. */ |
| ref = MAY_LJMP(hlua_checkfunction(L, 3)); |
| |
| /* Fourth argument : number of mandatory arguments expected on the configuration line. */ |
| if (lua_gettop(L) >= 4) |
| nargs = MAY_LJMP(luaL_checkinteger(L, 4)); |
| |
| /* browse the second argument as an array. */ |
| lua_pushnil(L); |
| while (lua_next(L, 2) != 0) { |
| if (lua_type(L, -1) != LUA_TSTRING) { |
| hlua_unref(L, ref); |
| WILL_LJMP(luaL_error(L, "register_action: second argument must be a table of strings")); |
| } |
| |
| /* Check if action exists */ |
| trash = get_trash_chunk(); |
| chunk_printf(trash, "lua.%s", name); |
| if (strcmp(lua_tostring(L, -1), "tcp-req") == 0) { |
| akw = tcp_req_cont_action(trash->area); |
| } else if (strcmp(lua_tostring(L, -1), "tcp-res") == 0) { |
| akw = tcp_res_cont_action(trash->area); |
| } else if (strcmp(lua_tostring(L, -1), "http-req") == 0) { |
| akw = action_http_req_custom(trash->area); |
| } else if (strcmp(lua_tostring(L, -1), "http-res") == 0) { |
| akw = action_http_res_custom(trash->area); |
| } else { |
| akw = NULL; |
| } |
| if (akw != NULL) { |
| fcn = akw->private; |
| if (fcn->function_ref[hlua_state_id] != -1) { |
| ha_warning("Trying to register action 'lua.%s' more than once. " |
| "This will become a hard error in version 2.5.\n", name); |
| hlua_unref(L, fcn->function_ref[hlua_state_id]); |
| } |
| fcn->function_ref[hlua_state_id] = ref; |
| |
| /* pop the environment string. */ |
| lua_pop(L, 1); |
| continue; |
| } |
| |
| /* Check required environment. Only accepted "http" or "tcp". */ |
| /* Allocate and fill the sample fetch keyword struct. */ |
| akl = calloc(1, sizeof(*akl) + sizeof(struct action_kw) * 2); |
| if (!akl) |
| goto alloc_error;; |
| fcn = new_hlua_function(); |
| if (!fcn) |
| goto alloc_error; |
| |
| /* Fill fcn. */ |
| fcn->name = strdup(name); |
| if (!fcn->name) |
| goto alloc_error; |
| fcn->function_ref[hlua_state_id] = ref; |
| |
| /* Set the expected number of arguments. */ |
| fcn->nargs = nargs; |
| |
| /* List head */ |
| akl->list.n = akl->list.p = NULL; |
| |
| /* action keyword. */ |
| len = strlen("lua.") + strlen(name) + 1; |
| akl->kw[0].kw = calloc(1, len); |
| if (!akl->kw[0].kw) |
| goto alloc_error; |
| |
| snprintf((char *)akl->kw[0].kw, len, "lua.%s", name); |
| |
| akl->kw[0].flags = 0; |
| akl->kw[0].private = fcn; |
| akl->kw[0].parse = action_register_lua; |
| |
| /* select the action registering point. */ |
| if (strcmp(lua_tostring(L, -1), "tcp-req") == 0) |
| tcp_req_cont_keywords_register(akl); |
| else if (strcmp(lua_tostring(L, -1), "tcp-res") == 0) |
| tcp_res_cont_keywords_register(akl); |
| else if (strcmp(lua_tostring(L, -1), "http-req") == 0) |
| http_req_keywords_register(akl); |
| else if (strcmp(lua_tostring(L, -1), "http-res") == 0) |
| http_res_keywords_register(akl); |
| else { |
| release_hlua_function(fcn); |
| hlua_unref(L, ref); |
| if (akl) |
| ha_free((char **)&(akl->kw[0].kw)); |
| ha_free(&akl); |
| WILL_LJMP(luaL_error(L, "Lua action environment '%s' is unknown. " |
| "'tcp-req', 'tcp-res', 'http-req' or 'http-res' " |
| "are expected.", lua_tostring(L, -1))); |
| } |
| |
| /* pop the environment string. */ |
| lua_pop(L, 1); |
| |
| /* reset for next loop */ |
| akl = NULL; |
| fcn = NULL; |
| } |
| return ACT_RET_PRS_OK; |
| |
| alloc_error: |
| release_hlua_function(fcn); |
| hlua_unref(L, ref); |
| ha_free(&akl); |
| WILL_LJMP(luaL_error(L, "Lua out of memory error.")); |
| return 0; /* Never reached */ |
| } |
| |
| static enum act_parse_ret action_register_service_tcp(const char **args, int *cur_arg, struct proxy *px, |
| struct act_rule *rule, char **err) |
| { |
| struct hlua_function *fcn = rule->kw->private; |
| |
| if (px->mode == PR_MODE_HTTP) { |
| memprintf(err, "Lua TCP services cannot be used on HTTP proxies"); |
| return ACT_RET_PRS_ERR; |
| } |
| |
| /* Memory for the rule. */ |
| rule->arg.hlua_rule = calloc(1, sizeof(*rule->arg.hlua_rule)); |
| if (!rule->arg.hlua_rule) { |
| memprintf(err, "out of memory error"); |
| return ACT_RET_PRS_ERR; |
| } |
| |
| /* Reference the Lua function and store the reference. */ |
| rule->arg.hlua_rule->fcn = fcn; |
| |
| /* TODO: later accept arguments. */ |
| rule->arg.hlua_rule->args = NULL; |
| |
| /* Add applet pointer in the rule. */ |
| rule->applet.obj_type = OBJ_TYPE_APPLET; |
| rule->applet.name = fcn->name; |
| rule->applet.init = hlua_applet_tcp_init; |
| rule->applet.fct = hlua_applet_tcp_fct; |
| rule->applet.release = hlua_applet_tcp_release; |
| rule->applet.timeout = hlua_timeout_applet; |
| |
| return 0; |
| } |
| |
| /* This function is an LUA binding used for registering |
| * "sample-conv" functions. It expects a converter name used |
| * in the haproxy configuration file, and an LUA function. |
| */ |
| __LJMP static int hlua_register_service(lua_State *L) |
| { |
| struct action_kw_list *akl; |
| const char *name; |
| const char *env; |
| int ref; |
| int len; |
| struct hlua_function *fcn = NULL; |
| struct buffer *trash; |
| struct action_kw *akw; |
| |
| MAY_LJMP(check_args(L, 3, "register_service")); |
| |
| if (hlua_gethlua(L)) { |
| /* runtime processing */ |
| WILL_LJMP(luaL_error(L, "register_service: not available outside of body context")); |
| } |
| |
| /* First argument : converter name. */ |
| name = MAY_LJMP(luaL_checkstring(L, 1)); |
| |
| /* Second argument : environment. */ |
| env = MAY_LJMP(luaL_checkstring(L, 2)); |
| |
| /* Third argument : lua function. */ |
| ref = MAY_LJMP(hlua_checkfunction(L, 3)); |
| |
| /* Check for service already registered */ |
| trash = get_trash_chunk(); |
| chunk_printf(trash, "lua.%s", name); |
| akw = service_find(trash->area); |
| if (akw != NULL) { |
| fcn = akw->private; |
| if (fcn->function_ref[hlua_state_id] != -1) { |
| ha_warning("Trying to register service 'lua.%s' more than once. " |
| "This will become a hard error in version 2.5.\n", name); |
| hlua_unref(L, fcn->function_ref[hlua_state_id]); |
| } |
| fcn->function_ref[hlua_state_id] = ref; |
| return 0; |
| } |
| |
| /* Allocate and fill the sample fetch keyword struct. */ |
| akl = calloc(1, sizeof(*akl) + sizeof(struct action_kw) * 2); |
| if (!akl) |
| goto alloc_error; |
| fcn = new_hlua_function(); |
| if (!fcn) |
| goto alloc_error; |
| |
| /* Fill fcn. */ |
| len = strlen("<lua.>") + strlen(name) + 1; |
| fcn->name = calloc(1, len); |
| if (!fcn->name) |
| goto alloc_error; |
| snprintf((char *)fcn->name, len, "<lua.%s>", name); |
| fcn->function_ref[hlua_state_id] = ref; |
| |
| /* List head */ |
| akl->list.n = akl->list.p = NULL; |
| |
| /* converter keyword. */ |
| len = strlen("lua.") + strlen(name) + 1; |
| akl->kw[0].kw = calloc(1, len); |
| if (!akl->kw[0].kw) |
| goto alloc_error; |
| |
| snprintf((char *)akl->kw[0].kw, len, "lua.%s", name); |
| |
| /* Check required environment. Only accepted "http" or "tcp". */ |
| if (strcmp(env, "tcp") == 0) |
| akl->kw[0].parse = action_register_service_tcp; |
| else if (strcmp(env, "http") == 0) |
| akl->kw[0].parse = action_register_service_http; |
| else { |
| release_hlua_function(fcn); |
| hlua_unref(L, ref); |
| if (akl) |
| ha_free((char **)&(akl->kw[0].kw)); |
| ha_free(&akl); |
| WILL_LJMP(luaL_error(L, "Lua service environment '%s' is unknown. " |
| "'tcp' or 'http' are expected.", env)); |
| } |
| |
| akl->kw[0].flags = 0; |
| akl->kw[0].private = fcn; |
| |
| /* End of array. */ |
| memset(&akl->kw[1], 0, sizeof(*akl->kw)); |
| |
| /* Register this new converter */ |
| service_keywords_register(akl); |
| |
| return 0; |
| |
| alloc_error: |
| release_hlua_function(fcn); |
| hlua_unref(L, ref); |
| ha_free(&akl); |
| WILL_LJMP(luaL_error(L, "Lua out of memory error.")); |
| return 0; /* Never reached */ |
| } |
| |
| /* This function initialises Lua cli handler. It copies the |
| * arguments in the Lua stack and create channel IO objects. |
| */ |
| static int hlua_cli_parse_fct(char **args, char *payload, struct appctx *appctx, void *private) |
| { |
| struct hlua_cli_ctx *ctx = applet_reserve_svcctx(appctx, sizeof(*ctx)); |
| struct hlua *hlua; |
| struct hlua_function *fcn; |
| int i; |
| const char *error; |
| |
| fcn = private; |
| ctx->fcn = private; |
| |
| hlua = pool_alloc(pool_head_hlua); |
| if (!hlua) { |
| SEND_ERR(NULL, "Lua cli '%s': out of memory.\n", fcn->name); |
| return 1; |
| } |
| HLUA_INIT(hlua); |
| ctx->hlua = hlua; |
| |
| /* Create task used by signal to wakeup applets. |
| * We use the same wakeup function than the Lua applet_tcp and |
| * applet_http. It is absolutely compatible. |
| */ |
| ctx->task = task_new_here(); |
| if (!ctx->task) { |
| SEND_ERR(NULL, "Lua cli '%s': out of memory.\n", fcn->name); |
| goto error; |
| } |
| ctx->task->nice = 0; |
| ctx->task->context = appctx; |
| ctx->task->process = hlua_applet_wakeup; |
| |
| /* Initialises the Lua context */ |
| if (!hlua_ctx_init(hlua, fcn_ref_to_stack_id(fcn), ctx->task)) { |
| SEND_ERR(NULL, "Lua cli '%s': can't initialize Lua context.\n", fcn->name); |
| goto error; |
| } |
| |
| /* The following Lua calls can fail. */ |
| if (!SET_SAFE_LJMP(hlua)) { |
| if (lua_type(hlua->T, -1) == LUA_TSTRING) |
| error = lua_tostring(hlua->T, -1); |
| else |
| error = "critical error"; |
| SEND_ERR(NULL, "Lua cli '%s': %s.\n", fcn->name, error); |
| goto error; |
| } |
| |
| /* Check stack available size. */ |
| if (!lua_checkstack(hlua->T, 2)) { |
| SEND_ERR(NULL, "Lua cli '%s': full stack.\n", fcn->name); |
| goto error; |
| } |
| |
| /* Restore the function in the stack. */ |
| hlua_pushref(hlua->T, fcn->function_ref[hlua->state_id]); |
| |
| /* Once the arguments parsed, the CLI is like an AppletTCP, |
| * so push AppletTCP in the stack. |
| */ |
| if (!hlua_applet_tcp_new(hlua->T, appctx)) { |
| SEND_ERR(NULL, "Lua cli '%s': full stack.\n", fcn->name); |
| goto error; |
| } |
| hlua->nargs = 1; |
| |
| /* push keywords in the stack. */ |
| for (i = 0; *args[i]; i++) { |
| /* Check stack available size. */ |
| if (!lua_checkstack(hlua->T, 1)) { |
| SEND_ERR(NULL, "Lua cli '%s': full stack.\n", fcn->name); |
| goto error; |
| } |
| lua_pushstring(hlua->T, args[i]); |
| hlua->nargs++; |
| } |
| |
| /* We must initialize the execution timeouts. */ |
| hlua_timer_init(&hlua->timer, hlua_timeout_session); |
| |
| /* At this point the execution is safe. */ |
| RESET_SAFE_LJMP(hlua); |
| |
| /* It's ok */ |
| return 0; |
| |
| /* It's not ok. */ |
| error: |
| RESET_SAFE_LJMP(hlua); |
| hlua_ctx_destroy(hlua); |
| ctx->hlua = NULL; |
| return 1; |
| } |
| |
| static int hlua_cli_io_handler_fct(struct appctx *appctx) |
| { |
| struct hlua_cli_ctx *ctx = appctx->svcctx; |
| struct hlua *hlua; |
| struct stconn *sc; |
| struct hlua_function *fcn; |
| |
| hlua = ctx->hlua; |
| sc = appctx_sc(appctx); |
| fcn = ctx->fcn; |
| |
| /* Execute the function. */ |
| switch (hlua_ctx_resume(hlua, 1)) { |
| |
| /* finished. */ |
| case HLUA_E_OK: |
| return 1; |
| |
| /* yield. */ |
| case HLUA_E_AGAIN: |
| /* We want write. */ |
| if (HLUA_IS_WAKERESWR(hlua)) |
| sc_need_room(sc, -1); |
| /* Set the timeout. */ |
| if (hlua->wake_time != TICK_ETERNITY) |
| task_schedule(hlua->task, hlua->wake_time); |
| return 0; |
| |
| /* finished with error. */ |
| case HLUA_E_ERRMSG: |
| /* Display log. */ |
| SEND_ERR(NULL, "Lua cli '%s': %s.\n", |
| fcn->name, lua_tostring(hlua->T, -1)); |
| lua_pop(hlua->T, 1); |
| return 1; |
| |
| case HLUA_E_ETMOUT: |
| SEND_ERR(NULL, "Lua converter '%s': execution timeout.\n", |
| fcn->name); |
| return 1; |
| |
| case HLUA_E_NOMEM: |
| SEND_ERR(NULL, "Lua converter '%s': out of memory error.\n", |
| fcn->name); |
| return 1; |
| |
| case HLUA_E_YIELD: /* unexpected */ |
| SEND_ERR(NULL, "Lua converter '%s': yield not allowed.\n", |
| fcn->name); |
| return 1; |
| |
| case HLUA_E_ERR: |
| /* Display log. */ |
| SEND_ERR(NULL, "Lua cli '%s' return an unknown error.\n", |
| fcn->name); |
| return 1; |
| |
| default: |
| return 1; |
| } |
| |
| return 1; |
| } |
| |
| static void hlua_cli_io_release_fct(struct appctx *appctx) |
| { |
| struct hlua_cli_ctx *ctx = appctx->svcctx; |
| |
| hlua_ctx_destroy(ctx->hlua); |
| ctx->hlua = NULL; |
| } |
| |
| /* This function is an LUA binding used for registering |
| * new keywords in the cli. It expects a list of keywords |
| * which are the "path". It is limited to 5 keywords. A |
| * description of the command, a function to be executed |
| * for the parsing and a function for io handlers. |
| */ |
| __LJMP static int hlua_register_cli(lua_State *L) |
| { |
| struct cli_kw_list *cli_kws; |
| const char *message; |
| int ref_io; |
| int len; |
| struct hlua_function *fcn = NULL; |
| int index; |
| int i; |
| struct buffer *trash; |
| const char *kw[5]; |
| struct cli_kw *cli_kw; |
| const char *errmsg; |
| char *end; |
| |
| MAY_LJMP(check_args(L, 3, "register_cli")); |
| |
| if (hlua_gethlua(L)) { |
| /* runtime processing */ |
| WILL_LJMP(luaL_error(L, "register_cli: not available outside of body context")); |
| } |
| |
| /* First argument : an array of maximum 5 keywords. */ |
| if (!lua_istable(L, 1)) |
| WILL_LJMP(luaL_argerror(L, 1, "1st argument must be a table")); |
| |
| /* Second argument : string with contextual message. */ |
| message = MAY_LJMP(luaL_checkstring(L, 2)); |
| |
| /* Third and fourth argument : lua function. */ |
| ref_io = MAY_LJMP(hlua_checkfunction(L, 3)); |
| |
| /* Check for CLI service already registered */ |
| trash = get_trash_chunk(); |
| index = 0; |
| lua_pushnil(L); |
| memset(kw, 0, sizeof(kw)); |
| while (lua_next(L, 1) != 0) { |
| if (index >= CLI_PREFIX_KW_NB) { |
| hlua_unref(L, ref_io); |
| WILL_LJMP(luaL_argerror(L, 1, "1st argument must be a table with a maximum of 5 entries")); |
| } |
| if (lua_type(L, -1) != LUA_TSTRING) { |
| hlua_unref(L, ref_io); |
| WILL_LJMP(luaL_argerror(L, 1, "1st argument must be a table filled with strings")); |
| } |
| kw[index] = lua_tostring(L, -1); |
| if (index == 0) |
| chunk_printf(trash, "%s", kw[index]); |
| else |
| chunk_appendf(trash, " %s", kw[index]); |
| index++; |
| lua_pop(L, 1); |
| } |
| cli_kw = cli_find_kw_exact((char **)kw); |
| if (cli_kw != NULL) { |
| fcn = cli_kw->private; |
| if (fcn->function_ref[hlua_state_id] != -1) { |
| ha_warning("Trying to register CLI keyword 'lua.%s' more than once. " |
| "This will become a hard error in version 2.5.\n", trash->area); |
| hlua_unref(L, fcn->function_ref[hlua_state_id]); |
| } |
| fcn->function_ref[hlua_state_id] = ref_io; |
| return 0; |
| } |
| |
| /* Allocate and fill the sample fetch keyword struct. */ |
| cli_kws = calloc(1, sizeof(*cli_kws) + sizeof(struct cli_kw) * 2); |
| if (!cli_kws) { |
| errmsg = "Lua out of memory error."; |
| goto error; |
| } |
| fcn = new_hlua_function(); |
| if (!fcn) { |
| errmsg = "Lua out of memory error."; |
| goto error; |
| } |
| |
| /* Fill path. */ |
| index = 0; |
| lua_pushnil(L); |
| while(lua_next(L, 1) != 0) { |
| if (index >= 5) { |
| errmsg = "1st argument must be a table with a maximum of 5 entries"; |
| goto error; |
| } |
| if (lua_type(L, -1) != LUA_TSTRING) { |
| errmsg = "1st argument must be a table filled with strings"; |
| goto error; |
| } |
| cli_kws->kw[0].str_kw[index] = strdup(lua_tostring(L, -1)); |
| if (!cli_kws->kw[0].str_kw[index]) { |
| errmsg = "Lua out of memory error."; |
| goto error; |
| } |
| index++; |
| lua_pop(L, 1); |
| } |
| |
| /* Copy help message. */ |
| cli_kws->kw[0].usage = strdup(message); |
| if (!cli_kws->kw[0].usage) { |
| errmsg = "Lua out of memory error."; |
| goto error; |
| } |
| |
| /* Fill fcn io handler. */ |
| len = strlen("<lua.cli>") + 1; |
| for (i = 0; i < index; i++) |
| len += strlen(cli_kws->kw[0].str_kw[i]) + 1; |
| fcn->name = calloc(1, len); |
| if (!fcn->name) { |
| errmsg = "Lua out of memory error."; |
| goto error; |
| } |
| |
| end = fcn->name; |
| len = 8; |
| memcpy(end, "<lua.cli", len); |
| end += len; |
| |
| for (i = 0; i < index; i++) { |
| *(end++) = '.'; |
| len = strlen(cli_kws->kw[0].str_kw[i]); |
| memcpy(end, cli_kws->kw[0].str_kw[i], len); |
| end += len; |
| } |
| *(end++) = '>'; |
| *(end++) = 0; |
| |
| fcn->function_ref[hlua_state_id] = ref_io; |
| |
| /* Fill last entries. */ |
| cli_kws->kw[0].private = fcn; |
| cli_kws->kw[0].parse = hlua_cli_parse_fct; |
| cli_kws->kw[0].io_handler = hlua_cli_io_handler_fct; |
| cli_kws->kw[0].io_release = hlua_cli_io_release_fct; |
| |
| /* Register this new converter */ |
| cli_register_kw(cli_kws); |
| |
| return 0; |
| |
| error: |
| release_hlua_function(fcn); |
| hlua_unref(L, ref_io); |
| if (cli_kws) { |
| for (i = 0; i < index; i++) |
| ha_free((char **)&(cli_kws->kw[0].str_kw[i])); |
| ha_free((char **)&(cli_kws->kw[0].usage)); |
| } |
| ha_free(&cli_kws); |
| WILL_LJMP(luaL_error(L, errmsg)); |
| return 0; /* Never reached */ |
| } |
| |
| static int hlua_filter_init_per_thread(struct proxy *px, struct flt_conf *fconf) |
| { |
| struct hlua_flt_config *conf = fconf->conf; |
| lua_State *L; |
| int error, pos, state_id, flt_ref; |
| |
| state_id = reg_flt_to_stack_id(conf->reg); |
| L = hlua_states[state_id]; |
| pos = lua_gettop(L); |
| |
| /* The filter parsing function */ |
| hlua_pushref(L, conf->reg->fun_ref[state_id]); |
| |
| /* Push the filter class on the stack and resolve all callbacks */ |
| hlua_pushref(L, conf->reg->flt_ref[state_id]); |
| |
| /* Duplicate the filter class so each filter will have its own copy */ |
| lua_newtable(L); |
| lua_pushnil(L); |
| |
| while (lua_next(L, pos+2)) { |
| lua_pushvalue(L, -2); |
| lua_insert(L, -2); |
| lua_settable(L, -4); |
| } |
| flt_ref = hlua_ref(L); |
| |
| /* Remove the original lua filter class from the stack */ |
| lua_pop(L, 1); |
| |
| /* Push the copy on the stack */ |
| hlua_pushref(L, flt_ref); |
| |
| /* extra args are pushed in a table */ |
| lua_newtable(L); |
| for (pos = 0; conf->args[pos]; pos++) { |
| /* Check stack available size. */ |
| if (!lua_checkstack(L, 1)) { |
| ha_alert("Lua filter '%s' : Lua error : full stack.", conf->reg->name); |
| goto error; |
| } |
| lua_pushstring(L, conf->args[pos]); |
| lua_rawseti(L, -2, lua_rawlen(L, -2) + 1); |
| } |
| |
| error = lua_pcall(L, 2, LUA_MULTRET, 0); |
| switch (error) { |
| case LUA_OK: |
| /* replace the filter ref */ |
| conf->ref[state_id] = flt_ref; |
| break; |
| case LUA_ERRRUN: |
| ha_alert("Lua filter '%s' : runtime error : %s", conf->reg->name, lua_tostring(L, -1)); |
| goto error; |
| case LUA_ERRMEM: |
| ha_alert("Lua filter '%s' : out of memory error", conf->reg->name); |
| goto error; |
| case LUA_ERRERR: |
| ha_alert("Lua filter '%s' : message handler error : %s", conf->reg->name, lua_tostring(L, -1)); |
| goto error; |
| #if defined(LUA_VERSION_NUM) && LUA_VERSION_NUM <= 503 |
| case LUA_ERRGCMM: |
| ha_alert("Lua filter '%s' : garbage collector error : %s", conf->reg->name, lua_tostring(L, -1)); |
| goto error; |
| #endif |
| default: |
| ha_alert("Lua filter '%s' : unknown error : %s", conf->reg->name, lua_tostring(L, -1)); |
| goto error; |
| } |
| |
| lua_settop(L, 0); |
| return 0; |
| |
| error: |
| lua_settop(L, 0); |
| return -1; |
| } |
| |
| static void hlua_filter_deinit_per_thread(struct proxy *px, struct flt_conf *fconf) |
| { |
| struct hlua_flt_config *conf = fconf->conf; |
| lua_State *L; |
| int state_id; |
| |
| if (!conf) |
| return; |
| |
| state_id = reg_flt_to_stack_id(conf->reg); |
| L = hlua_states[state_id]; |
| hlua_unref(L, conf->ref[state_id]); |
| } |
| |
| static int hlua_filter_init(struct proxy *px, struct flt_conf *fconf) |
| { |
| struct hlua_flt_config *conf = fconf->conf; |
| int state_id = reg_flt_to_stack_id(conf->reg); |
| |
| /* Rely on per-thread init for global scripts */ |
| if (!state_id) |
| return hlua_filter_init_per_thread(px, fconf); |
| return 0; |
| } |
| |
| static void hlua_filter_deinit(struct proxy *px, struct flt_conf *fconf) |
| { |
| |
| if (fconf->conf) { |
| struct hlua_flt_config *conf = fconf->conf; |
| int state_id = reg_flt_to_stack_id(conf->reg); |
| int pos; |
| |
| /* Rely on per-thread deinit for global scripts */ |
| if (!state_id) |
| hlua_filter_deinit_per_thread(px, fconf); |
| |
| for (pos = 0; conf->args[pos]; pos++) |
| free(conf->args[pos]); |
| free(conf->args); |
| } |
| ha_free(&fconf->conf); |
| ha_free((char **)&fconf->id); |
| ha_free(&fconf->ops); |
| } |
| |
| static int hlua_filter_new(struct stream *s, struct filter *filter) |
| { |
| struct hlua_flt_config *conf = FLT_CONF(filter); |
| struct hlua_flt_ctx *flt_ctx = NULL; |
| int ret = 1; |
| |
| /* In the execution wrappers linked with a stream, the |
| * Lua context can be not initialized. This behavior |
| * permits to save performances because a systematic |
| * Lua initialization cause 5% performances loss. |
| */ |
| if (!s->hlua) { |
| struct hlua *hlua; |
| |
| hlua = pool_alloc(pool_head_hlua); |
| if (!hlua) { |
| SEND_ERR(s->be, "Lua filter '%s': can't initialize Lua context.\n", |
| conf->reg->name); |
| ret = 0; |
| goto end; |
| } |
| HLUA_INIT(hlua); |
| s->hlua = hlua; |
| if (!hlua_ctx_init(s->hlua, reg_flt_to_stack_id(conf->reg), s->task)) { |
| SEND_ERR(s->be, "Lua filter '%s': can't initialize Lua context.\n", |
| conf->reg->name); |
| ret = 0; |
| goto end; |
| } |
| } |
| |
| flt_ctx = pool_zalloc(pool_head_hlua_flt_ctx); |
| if (!flt_ctx) { |
| SEND_ERR(s->be, "Lua filter '%s': can't initialize filter Lua context.\n", |
| conf->reg->name); |
| ret = 0; |
| goto end; |
| } |
| flt_ctx->hlua[0] = pool_alloc(pool_head_hlua); |
| flt_ctx->hlua[1] = pool_alloc(pool_head_hlua); |
| if (!flt_ctx->hlua[0] || !flt_ctx->hlua[1]) { |
| SEND_ERR(s->be, "Lua filter '%s': can't initialize filter Lua context.\n", |
| conf->reg->name); |
| ret = 0; |
| goto end; |
| } |
| HLUA_INIT(flt_ctx->hlua[0]); |
| HLUA_INIT(flt_ctx->hlua[1]); |
| if (!hlua_ctx_init(flt_ctx->hlua[0], reg_flt_to_stack_id(conf->reg), s->task) || |
| !hlua_ctx_init(flt_ctx->hlua[1], reg_flt_to_stack_id(conf->reg), s->task)) { |
| SEND_ERR(s->be, "Lua filter '%s': can't initialize filter Lua context.\n", |
| conf->reg->name); |
| ret = 0; |
| goto end; |
| } |
| |
| if (!HLUA_IS_RUNNING(s->hlua)) { |
| /* The following Lua calls can fail. */ |
| if (!SET_SAFE_LJMP(s->hlua)) { |
| const char *error; |
| |
| if (lua_type(s->hlua->T, -1) == LUA_TSTRING) |
| error = lua_tostring(s->hlua->T, -1); |
| else |
| error = "critical error"; |
| SEND_ERR(s->be, "Lua filter '%s': %s.\n", conf->reg->name, error); |
| ret = 0; |
| goto end; |
| } |
| |
| /* Check stack size. */ |
| if (!lua_checkstack(s->hlua->T, 1)) { |
| SEND_ERR(s->be, "Lua filter '%s': full stack.\n", conf->reg->name); |
| RESET_SAFE_LJMP(s->hlua); |
| ret = 0; |
| goto end; |
| } |
| |
| hlua_pushref(s->hlua->T, conf->ref[s->hlua->state_id]); |
| if (lua_getfield(s->hlua->T, -1, "new") != LUA_TFUNCTION) { |
| SEND_ERR(s->be, "Lua filter '%s': 'new' field is not a function.\n", |
| conf->reg->name); |
| RESET_SAFE_LJMP(s->hlua); |
| ret = 0; |
| goto end; |
| } |
| lua_insert(s->hlua->T, -2); |
| |
| /* Push the copy on the stack */ |
| s->hlua->nargs = 1; |
| |
| /* We must initialize the execution timeouts. */ |
| hlua_timer_init(&s->hlua->timer, hlua_timeout_session); |
| |
| /* At this point the execution is safe. */ |
| RESET_SAFE_LJMP(s->hlua); |
| } |
| |
| switch (hlua_ctx_resume(s->hlua, 0)) { |
| case HLUA_E_OK: |
| /* Nothing returned or not a table, ignore the filter for current stream */ |
| if (!lua_gettop(s->hlua->T) || !lua_istable(s->hlua->T, 1)) { |
| ret = 0; |
| goto end; |
| } |
| |
| /* Attached the filter pointer to the ctx */ |
| lua_pushstring(s->hlua->T, "__filter"); |
| lua_pushlightuserdata(s->hlua->T, filter); |
| lua_settable(s->hlua->T, -3); |
| |
| /* Save a ref on the filter ctx */ |
| lua_pushvalue(s->hlua->T, 1); |
| flt_ctx->ref = hlua_ref(s->hlua->T); |
| filter->ctx = flt_ctx; |
| break; |
| case HLUA_E_ERRMSG: |
| SEND_ERR(s->be, "Lua filter '%s' : %s.\n", conf->reg->name, lua_tostring(s->hlua->T, -1)); |
| ret = -1; |
| goto end; |
| case HLUA_E_ETMOUT: |
| SEND_ERR(s->be, "Lua filter '%s' : 'new' execution timeout.\n", conf->reg->name); |
| ret = 0; |
| goto end; |
| case HLUA_E_NOMEM: |
| SEND_ERR(s->be, "Lua filter '%s' : out of memory error.\n", conf->reg->name); |
| ret = 0; |
| goto end; |
| case HLUA_E_AGAIN: |
| case HLUA_E_YIELD: |
| SEND_ERR(s->be, "Lua filter '%s': yield functions like core.tcp() or core.sleep()" |
| " are not allowed from 'new' function.\n", conf->reg->name); |
| ret = 0; |
| goto end; |
| case HLUA_E_ERR: |
| SEND_ERR(s->be, "Lua filter '%s': 'new' returns an unknown error.\n", conf->reg->name); |
| ret = 0; |
| goto end; |
| default: |
| ret = 0; |
| goto end; |
| } |
| |
| end: |
| if (s->hlua) |
| lua_settop(s->hlua->T, 0); |
| if (ret <= 0) { |
| if (flt_ctx) { |
| hlua_ctx_destroy(flt_ctx->hlua[0]); |
| hlua_ctx_destroy(flt_ctx->hlua[1]); |
| pool_free(pool_head_hlua_flt_ctx, flt_ctx); |
| } |
| } |
| return ret; |
| } |
| |
| static void hlua_filter_delete(struct stream *s, struct filter *filter) |
| { |
| struct hlua_flt_ctx *flt_ctx = filter->ctx; |
| |
| hlua_unref(s->hlua->T, flt_ctx->ref); |
| hlua_ctx_destroy(flt_ctx->hlua[0]); |
| hlua_ctx_destroy(flt_ctx->hlua[1]); |
| pool_free(pool_head_hlua_flt_ctx, flt_ctx); |
| filter->ctx = NULL; |
| } |
| |
| static int hlua_filter_from_payload(struct filter *filter) |
| { |
| struct hlua_flt_ctx *flt_ctx = filter->ctx; |
| |
| return (flt_ctx && !!(flt_ctx->flags & HLUA_FLT_CTX_FL_PAYLOAD)); |
| } |
| |
| static int hlua_filter_callback(struct stream *s, struct filter *filter, const char *fun, |
| int dir, unsigned int flags) |
| { |
| struct hlua *flt_hlua; |
| struct hlua_flt_config *conf = FLT_CONF(filter); |
| struct hlua_flt_ctx *flt_ctx = filter->ctx; |
| unsigned int hflags = HLUA_TXN_FLT_CTX; |
| int ret = 1; |
| |
| flt_hlua = flt_ctx->hlua[(dir == SMP_OPT_DIR_REQ ? 0 : 1)]; |
| if (!flt_hlua) |
| goto end; |
| |
| if (!HLUA_IS_RUNNING(flt_hlua)) { |
| int extra_idx = lua_gettop(flt_hlua->T); |
| |
| /* The following Lua calls can fail. */ |
| if (!SET_SAFE_LJMP(flt_hlua)) { |
| const char *error; |
| |
| if (lua_type(flt_hlua->T, -1) == LUA_TSTRING) |
| error = lua_tostring(flt_hlua->T, -1); |
| else |
| error = "critical error"; |
| SEND_ERR(s->be, "Lua filter '%s': %s.\n", conf->reg->name, error); |
| goto end; |
| } |
| |
| /* Check stack size. */ |
| if (!lua_checkstack(flt_hlua->T, 3)) { |
| SEND_ERR(s->be, "Lua filter '%s': full stack.\n", conf->reg->name); |
| RESET_SAFE_LJMP(flt_hlua); |
| goto end; |
| } |
| |
| hlua_pushref(flt_hlua->T, flt_ctx->ref); |
| if (lua_getfield(flt_hlua->T, -1, fun) != LUA_TFUNCTION) { |
| RESET_SAFE_LJMP(flt_hlua); |
| goto end; |
| } |
| lua_insert(flt_hlua->T, -2); |
| |
| if (!hlua_txn_new(flt_hlua->T, s, s->be, dir, hflags)) { |
| SEND_ERR(s->be, "Lua filter '%s': full stack.\n", conf->reg->name); |
| RESET_SAFE_LJMP(flt_hlua); |
| goto end; |
| } |
| flt_hlua->nargs = 2; |
| |
| if (flags & HLUA_FLT_CB_ARG_CHN) { |
| if (dir == SMP_OPT_DIR_REQ) |
| lua_getfield(flt_hlua->T, -1, "req"); |
| else |
| lua_getfield(flt_hlua->T, -1, "res"); |
| if (lua_type(flt_hlua->T, -1) == LUA_TTABLE) { |
| lua_pushstring(flt_hlua->T, "__filter"); |
| lua_pushlightuserdata(flt_hlua->T, filter); |
| lua_settable(flt_hlua->T, -3); |
| } |
| flt_hlua->nargs++; |
| } |
| else if (flags & HLUA_FLT_CB_ARG_HTTP_MSG) { |
| if (dir == SMP_OPT_DIR_REQ) |
| lua_getfield(flt_hlua->T, -1, "http_req"); |
| else |
| lua_getfield(flt_hlua->T, -1, "http_res"); |
| if (lua_type(flt_hlua->T, -1) == LUA_TTABLE) { |
| lua_pushstring(flt_hlua->T, "__filter"); |
| lua_pushlightuserdata(flt_hlua->T, filter); |
| lua_settable(flt_hlua->T, -3); |
| } |
| flt_hlua->nargs++; |
| } |
| |
| /* Check stack size. */ |
| if (!lua_checkstack(flt_hlua->T, 1)) { |
| SEND_ERR(s->be, "Lua filter '%s': full stack.\n", conf->reg->name); |
| RESET_SAFE_LJMP(flt_hlua); |
| goto end; |
| } |
| |
| while (extra_idx--) { |
| lua_pushvalue(flt_hlua->T, 1); |
| lua_remove(flt_hlua->T, 1); |
| flt_hlua->nargs++; |
| } |
| |
| /* We must initialize the execution timeouts. */ |
| hlua_timer_init(&flt_hlua->timer, hlua_timeout_session); |
| |
| /* At this point the execution is safe. */ |
| RESET_SAFE_LJMP(flt_hlua); |
| } |
| |
| switch (hlua_ctx_resume(flt_hlua, !(flags & HLUA_FLT_CB_FINAL))) { |
| case HLUA_E_OK: |
| /* Catch the return value if it required */ |
| if ((flags & HLUA_FLT_CB_RETVAL) && lua_gettop(flt_hlua->T) > 0) { |
| ret = lua_tointeger(flt_hlua->T, -1); |
| lua_settop(flt_hlua->T, 0); /* Empty the stack. */ |
| } |
| |
| /* Set timeout in the required channel. */ |
| if (flt_hlua->wake_time != TICK_ETERNITY) { |
| if (dir == SMP_OPT_DIR_REQ) |
| s->req.analyse_exp = flt_hlua->wake_time; |
| else |
| s->res.analyse_exp = flt_hlua->wake_time; |
| } |
| break; |
| case HLUA_E_AGAIN: |
| /* Set timeout in the required channel. */ |
| if (flt_hlua->wake_time != TICK_ETERNITY) { |
| if (dir == SMP_OPT_DIR_REQ) |
| s->req.analyse_exp = flt_hlua->wake_time; |
| else |
| s->res.analyse_exp = flt_hlua->wake_time; |
| } |
| /* Some actions can be wake up when a "write" event |
| * is detected on a response channel. This is useful |
| * only for actions targeted on the requests. |
| */ |
| if (HLUA_IS_WAKERESWR(flt_hlua)) |
| s->res.flags |= CF_WAKE_WRITE; |
| if (HLUA_IS_WAKEREQWR(flt_hlua)) |
| s->req.flags |= CF_WAKE_WRITE; |
| ret = 0; |
| goto end; |
| case HLUA_E_ERRMSG: |
| SEND_ERR(s->be, "Lua filter '%s' : %s.\n", conf->reg->name, lua_tostring(flt_hlua->T, -1)); |
| ret = -1; |
| goto end; |
| case HLUA_E_ETMOUT: |
| SEND_ERR(s->be, "Lua filter '%s' : '%s' callback execution timeout.\n", conf->reg->name, fun); |
| goto end; |
| case HLUA_E_NOMEM: |
| SEND_ERR(s->be, "Lua filter '%s' : out of memory error.\n", conf->reg->name); |
| goto end; |
| case HLUA_E_YIELD: |
| SEND_ERR(s->be, "Lua filter '%s': yield functions like core.tcp() or core.sleep()" |
| " are not allowed from '%s' callback.\n", conf->reg->name, fun); |
| goto end; |
| case HLUA_E_ERR: |
| SEND_ERR(s->be, "Lua filter '%s': '%s' returns an unknown error.\n", conf->reg->name, fun); |
| goto end; |
| default: |
| goto end; |
| } |
| |
| |
| end: |
| return ret; |
| } |
| |
| static int hlua_filter_start_analyze(struct stream *s, struct filter *filter, struct channel *chn) |
| { |
| struct hlua_flt_ctx *flt_ctx = filter->ctx; |
| |
| flt_ctx->flags = 0; |
| return hlua_filter_callback(s, filter, "start_analyze", |
| (!(chn->flags & CF_ISRESP) ? SMP_OPT_DIR_REQ : SMP_OPT_DIR_RES), |
| (HLUA_FLT_CB_FINAL | HLUA_FLT_CB_RETVAL | HLUA_FLT_CB_ARG_CHN)); |
| } |
| |
| static int hlua_filter_end_analyze(struct stream *s, struct filter *filter, struct channel *chn) |
| { |
| struct hlua_flt_ctx *flt_ctx = filter->ctx; |
| |
| flt_ctx->flags &= ~HLUA_FLT_CTX_FL_PAYLOAD; |
| return hlua_filter_callback(s, filter, "end_analyze", |
| (!(chn->flags & CF_ISRESP) ? SMP_OPT_DIR_REQ : SMP_OPT_DIR_RES), |
| (HLUA_FLT_CB_FINAL | HLUA_FLT_CB_RETVAL | HLUA_FLT_CB_ARG_CHN)); |
| } |
| |
| static int hlua_filter_http_headers(struct stream *s, struct filter *filter, struct http_msg *msg) |
| { |
| struct hlua_flt_ctx *flt_ctx = filter->ctx; |
| |
| flt_ctx->flags &= ~HLUA_FLT_CTX_FL_PAYLOAD; |
| return hlua_filter_callback(s, filter, "http_headers", |
| (!(msg->chn->flags & CF_ISRESP) ? SMP_OPT_DIR_REQ : SMP_OPT_DIR_RES), |
| (HLUA_FLT_CB_FINAL | HLUA_FLT_CB_RETVAL | HLUA_FLT_CB_ARG_HTTP_MSG)); |
| } |
| |
| static int hlua_filter_http_payload(struct stream *s, struct filter *filter, struct http_msg *msg, |
| unsigned int offset, unsigned int len) |
| { |
| struct hlua_flt_ctx *flt_ctx = filter->ctx; |
| struct hlua *flt_hlua; |
| int dir = (!(msg->chn->flags & CF_ISRESP) ? SMP_OPT_DIR_REQ : SMP_OPT_DIR_RES); |
| int idx = (dir == SMP_OPT_DIR_REQ ? 0 : 1); |
| int ret; |
| |
| flt_hlua = flt_ctx->hlua[idx]; |
| flt_ctx->cur_off[idx] = offset; |
| flt_ctx->cur_len[idx] = len; |
| flt_ctx->flags |= HLUA_FLT_CTX_FL_PAYLOAD; |
| ret = hlua_filter_callback(s, filter, "http_payload", dir, (HLUA_FLT_CB_FINAL | HLUA_FLT_CB_ARG_HTTP_MSG)); |
| if (ret != -1) { |
| ret = flt_ctx->cur_len[idx]; |
| if (lua_gettop(flt_hlua->T) > 0) { |
| ret = lua_tointeger(flt_hlua->T, -1); |
| if (ret > flt_ctx->cur_len[idx]) |
| ret = flt_ctx->cur_len[idx]; |
| lua_settop(flt_hlua->T, 0); /* Empty the stack. */ |
| } |
| } |
| return ret; |
| } |
| |
| static int hlua_filter_http_end(struct stream *s, struct filter *filter, struct http_msg *msg) |
| { |
| struct hlua_flt_ctx *flt_ctx = filter->ctx; |
| |
| flt_ctx->flags &= ~HLUA_FLT_CTX_FL_PAYLOAD; |
| return hlua_filter_callback(s, filter, "http_end", |
| (!(msg->chn->flags & CF_ISRESP) ? SMP_OPT_DIR_REQ : SMP_OPT_DIR_RES), |
| (HLUA_FLT_CB_FINAL | HLUA_FLT_CB_RETVAL | HLUA_FLT_CB_ARG_HTTP_MSG)); |
| } |
| |
| static int hlua_filter_tcp_payload(struct stream *s, struct filter *filter, struct channel *chn, |
| unsigned int offset, unsigned int len) |
| { |
| struct hlua_flt_ctx *flt_ctx = filter->ctx; |
| struct hlua *flt_hlua; |
| int dir = (!(chn->flags & CF_ISRESP) ? SMP_OPT_DIR_REQ : SMP_OPT_DIR_RES); |
| int idx = (dir == SMP_OPT_DIR_REQ ? 0 : 1); |
| int ret; |
| |
| flt_hlua = flt_ctx->hlua[idx]; |
| flt_ctx->cur_off[idx] = offset; |
| flt_ctx->cur_len[idx] = len; |
| flt_ctx->flags |= HLUA_FLT_CTX_FL_PAYLOAD; |
| ret = hlua_filter_callback(s, filter, "tcp_payload", dir, (HLUA_FLT_CB_FINAL | HLUA_FLT_CB_ARG_CHN)); |
| if (ret != -1) { |
| ret = flt_ctx->cur_len[idx]; |
| if (lua_gettop(flt_hlua->T) > 0) { |
| ret = lua_tointeger(flt_hlua->T, -1); |
| if (ret > flt_ctx->cur_len[idx]) |
| ret = flt_ctx->cur_len[idx]; |
| lua_settop(flt_hlua->T, 0); /* Empty the stack. */ |
| } |
| } |
| return ret; |
| } |
| |
| static int hlua_filter_parse_fct(char **args, int *cur_arg, struct proxy *px, |
| struct flt_conf *fconf, char **err, void *private) |
| { |
| struct hlua_reg_filter *reg_flt = private; |
| lua_State *L; |
| struct hlua_flt_config *conf = NULL; |
| const char *flt_id = NULL; |
| int state_id, pos, flt_flags = 0; |
| struct flt_ops *hlua_flt_ops = NULL; |
| |
| state_id = reg_flt_to_stack_id(reg_flt); |
| L = hlua_states[state_id]; |
| |
| /* Initialize the filter ops with default callbacks */ |
| hlua_flt_ops = calloc(1, sizeof(*hlua_flt_ops)); |
| if (!hlua_flt_ops) |
| goto error; |
| hlua_flt_ops->init = hlua_filter_init; |
| hlua_flt_ops->deinit = hlua_filter_deinit; |
| if (state_id) { |
| /* Set per-thread callback if script is loaded per-thread */ |
| hlua_flt_ops->init_per_thread = hlua_filter_init_per_thread; |
| hlua_flt_ops->deinit_per_thread = hlua_filter_deinit_per_thread; |
| } |
| hlua_flt_ops->attach = hlua_filter_new; |
| hlua_flt_ops->detach = hlua_filter_delete; |
| |
| /* Push the filter class on the stack and resolve all callbacks */ |
| hlua_pushref(L, reg_flt->flt_ref[state_id]); |
| |
| if (lua_getfield(L, -1, "start_analyze") == LUA_TFUNCTION) |
| hlua_flt_ops->channel_start_analyze = hlua_filter_start_analyze; |
| lua_pop(L, 1); |
| if (lua_getfield(L, -1, "end_analyze") == LUA_TFUNCTION) |
| hlua_flt_ops->channel_end_analyze = hlua_filter_end_analyze; |
| lua_pop(L, 1); |
| if (lua_getfield(L, -1, "http_headers") == LUA_TFUNCTION) |
| hlua_flt_ops->http_headers = hlua_filter_http_headers; |
| lua_pop(L, 1); |
| if (lua_getfield(L, -1, "http_payload") == LUA_TFUNCTION) |
| hlua_flt_ops->http_payload = hlua_filter_http_payload; |
| lua_pop(L, 1); |
| if (lua_getfield(L, -1, "http_end") == LUA_TFUNCTION) |
| hlua_flt_ops->http_end = hlua_filter_http_end; |
| lua_pop(L, 1); |
| if (lua_getfield(L, -1, "tcp_payload") == LUA_TFUNCTION) |
| hlua_flt_ops->tcp_payload = hlua_filter_tcp_payload; |
| lua_pop(L, 1); |
| |
| /* Get id and flags of the filter class */ |
| if (lua_getfield(L, -1, "id") == LUA_TSTRING) |
| flt_id = lua_tostring(L, -1); |
| lua_pop(L, 1); |
| if (lua_getfield(L, -1, "flags") == LUA_TNUMBER) |
| flt_flags = lua_tointeger(L, -1); |
| lua_pop(L, 1); |
| |
| /* Create the filter config */ |
| conf = calloc(1, sizeof(*conf)); |
| if (!conf) |
| goto error; |
| conf->reg = reg_flt; |
| |
| /* duplicate args */ |
| for (pos = 0; *args[*cur_arg + 1 + pos]; pos++); |
| conf->args = calloc(pos + 1, sizeof(*conf->args)); |
| if (!conf->args) |
| goto error; |
| for (pos = 0; *args[*cur_arg + 1 + pos]; pos++) { |
| conf->args[pos] = strdup(args[*cur_arg + 1 + pos]); |
| if (!conf->args[pos]) |
| goto error; |
| } |
| conf->args[pos] = NULL; |
| *cur_arg += pos + 1; |
| |
| if (flt_id) { |
| fconf->id = strdup(flt_id); |
| if (!fconf->id) |
| goto error; |
| } |
| fconf->flags = flt_flags; |
| fconf->conf = conf; |
| fconf->ops = hlua_flt_ops; |
| |
| lua_settop(L, 0); |
| return 0; |
| |
| error: |
| memprintf(err, "Lua filter '%s' : Lua out of memory error", reg_flt->name); |
| free(hlua_flt_ops); |
| if (conf && conf->args) { |
| for (pos = 0; conf->args[pos]; pos++) |
| free(conf->args[pos]); |
| free(conf->args); |
| } |
| free(conf); |
| free((char *)fconf->id); |
| lua_settop(L, 0); |
| return -1; |
| } |
| |
| __LJMP static int hlua_register_data_filter(lua_State *L) |
| { |
| struct filter *filter; |
| struct channel *chn; |
| |
| MAY_LJMP(check_args(L, 2, "register_data_filter")); |
| MAY_LJMP(luaL_checktype(L, 1, LUA_TTABLE)); |
| chn = MAY_LJMP(hlua_checkchannel(L, 2)); |
| |
| lua_getfield(L, 1, "__filter"); |
| MAY_LJMP(luaL_checktype(L, -1, LUA_TLIGHTUSERDATA)); |
| filter = lua_touserdata (L, -1); |
| lua_pop(L, 1); |
| |
| register_data_filter(chn_strm(chn), chn, filter); |
| return 1; |
| } |
| |
| __LJMP static int hlua_unregister_data_filter(lua_State *L) |
| { |
| struct filter *filter; |
| struct channel *chn; |
| |
| MAY_LJMP(check_args(L, 2, "unregister_data_filter")); |
| MAY_LJMP(luaL_checktype(L, 1, LUA_TTABLE)); |
| chn = MAY_LJMP(hlua_checkchannel(L, 2)); |
| |
| lua_getfield(L, 1, "__filter"); |
| MAY_LJMP(luaL_checktype(L, -1, LUA_TLIGHTUSERDATA)); |
| filter = lua_touserdata (L, -1); |
| lua_pop(L, 1); |
| |
| unregister_data_filter(chn_strm(chn), chn, filter); |
| return 1; |
| } |
| |
| /* This function is an LUA binding used for registering a filter. It expects a |
| * filter name used in the haproxy configuration file and a LUA function to |
| * parse configuration arguments. |
| */ |
| __LJMP static int hlua_register_filter(lua_State *L) |
| { |
| struct buffer *trash; |
| struct flt_kw_list *fkl; |
| struct flt_kw *fkw; |
| const char *name; |
| struct hlua_reg_filter *reg_flt= NULL; |
| int flt_ref, fun_ref; |
| int len; |
| |
| MAY_LJMP(check_args(L, 3, "register_filter")); |
| |
| if (hlua_gethlua(L)) { |
| /* runtime processing */ |
| WILL_LJMP(luaL_error(L, "register_filter: not available outside of body context")); |
| } |
| |
| /* First argument : filter name. */ |
| name = MAY_LJMP(luaL_checkstring(L, 1)); |
| |
| /* Second argument : The filter class */ |
| flt_ref = MAY_LJMP(hlua_checktable(L, 2)); |
| |
| /* Third argument : lua function. */ |
| fun_ref = MAY_LJMP(hlua_checkfunction(L, 3)); |
| |
| trash = get_trash_chunk(); |
| chunk_printf(trash, "lua.%s", name); |
| fkw = flt_find_kw(trash->area); |
| if (fkw != NULL) { |
| reg_flt = fkw->private; |
| if (reg_flt->flt_ref[hlua_state_id] != -1 || reg_flt->fun_ref[hlua_state_id] != -1) { |
| ha_warning("Trying to register filter 'lua.%s' more than once. " |
| "This will become a hard error in version 2.5.\n", name); |
| if (reg_flt->flt_ref[hlua_state_id] != -1) |
| hlua_unref(L, reg_flt->flt_ref[hlua_state_id]); |
| if (reg_flt->fun_ref[hlua_state_id] != -1) |
| hlua_unref(L, reg_flt->fun_ref[hlua_state_id]); |
| } |
| reg_flt->flt_ref[hlua_state_id] = flt_ref; |
| reg_flt->fun_ref[hlua_state_id] = fun_ref; |
| return 0; |
| } |
| |
| fkl = calloc(1, sizeof(*fkl) + sizeof(struct flt_kw) * 2); |
| if (!fkl) |
| goto alloc_error; |
| fkl->scope = "HLUA"; |
| |
| reg_flt = new_hlua_reg_filter(name); |
| if (!reg_flt) |
| goto alloc_error; |
| |
| reg_flt->flt_ref[hlua_state_id] = flt_ref; |
| reg_flt->fun_ref[hlua_state_id] = fun_ref; |
| |
| /* The filter keyword */ |
| len = strlen("lua.") + strlen(name) + 1; |
| fkl->kw[0].kw = calloc(1, len); |
| if (!fkl->kw[0].kw) |
| goto alloc_error; |
| |
| snprintf((char *)fkl->kw[0].kw, len, "lua.%s", name); |
| |
| fkl->kw[0].parse = hlua_filter_parse_fct; |
| fkl->kw[0].private = reg_flt; |
| memset(&fkl->kw[1], 0, sizeof(*fkl->kw)); |
| |
| /* Register this new filter */ |
| flt_register_keywords(fkl); |
| |
| return 0; |
| |
| alloc_error: |
| release_hlua_reg_filter(reg_flt); |
| hlua_unref(L, flt_ref); |
| hlua_unref(L, fun_ref); |
| ha_free(&fkl); |
| WILL_LJMP(luaL_error(L, "Lua out of memory error.")); |
| return 0; /* Never reached */ |
| } |
| |
| static int hlua_read_timeout(char **args, int section_type, struct proxy *curpx, |
| const struct proxy *defpx, const char *file, int line, |
| char **err, unsigned int *timeout) |
| { |
| const char *error; |
| |
| error = parse_time_err(args[1], timeout, TIME_UNIT_MS); |
| if (error == PARSE_TIME_OVER) { |
| memprintf(err, "timer overflow in argument <%s> to <%s> (maximum value is 2147483647 ms or ~24.8 days)", |
| args[1], args[0]); |
| return -1; |
| } |
| else if (error == PARSE_TIME_UNDER) { |
| memprintf(err, "timer underflow in argument <%s> to <%s> (minimum non-null value is 1 ms)", |
| args[1], args[0]); |
| return -1; |
| } |
| else if (error) { |
| memprintf(err, "%s: invalid timeout", args[0]); |
| return -1; |
| } |
| return 0; |
| } |
| |
| static int hlua_burst_timeout(char **args, int section_type, struct proxy *curpx, |
| const struct proxy *defpx, const char *file, int line, |
| char **err) |
| { |
| return hlua_read_timeout(args, section_type, curpx, defpx, |
| file, line, err, &hlua_timeout_burst); |
| } |
| |
| static int hlua_session_timeout(char **args, int section_type, struct proxy *curpx, |
| const struct proxy *defpx, const char *file, int line, |
| char **err) |
| { |
| return hlua_read_timeout(args, section_type, curpx, defpx, |
| file, line, err, &hlua_timeout_session); |
| } |
| |
| static int hlua_task_timeout(char **args, int section_type, struct proxy *curpx, |
| const struct proxy *defpx, const char *file, int line, |
| char **err) |
| { |
| return hlua_read_timeout(args, section_type, curpx, defpx, |
| file, line, err, &hlua_timeout_task); |
| } |
| |
| static int hlua_applet_timeout(char **args, int section_type, struct proxy *curpx, |
| const struct proxy *defpx, const char *file, int line, |
| char **err) |
| { |
| return hlua_read_timeout(args, section_type, curpx, defpx, |
| file, line, err, &hlua_timeout_applet); |
| } |
| |
| static int hlua_forced_yield(char **args, int section_type, struct proxy *curpx, |
| const struct proxy *defpx, const char *file, int line, |
| char **err) |
| { |
| char *error; |
| |
| hlua_nb_instruction = strtoll(args[1], &error, 10); |
| if (*error != '\0') { |
| memprintf(err, "%s: invalid number", args[0]); |
| return -1; |
| } |
| return 0; |
| } |
| |
| static int hlua_parse_maxmem(char **args, int section_type, struct proxy *curpx, |
| const struct proxy *defpx, const char *file, int line, |
| char **err) |
| { |
| char *error; |
| |
| if (*(args[1]) == 0) { |
| memprintf(err, "'%s' expects an integer argument (Lua memory size in MB).", args[0]); |
| return -1; |
| } |
| hlua_global_allocator.limit = strtoll(args[1], &error, 10) * 1024L * 1024L; |
| if (*error != '\0') { |
| memprintf(err, "%s: invalid number %s (error at '%c')", args[0], args[1], *error); |
| return -1; |
| } |
| return 0; |
| } |
| |
| |
| /* This function is called by the main configuration key "lua-load". It loads and |
| * execute an lua file during the parsing of the HAProxy configuration file. It is |
| * the main lua entry point. |
| * |
| * This function runs with the HAProxy keywords API. It returns -1 if an error |
| * occurs, otherwise it returns 0. |
| * |
| * In some error case, LUA set an error message in top of the stack. This function |
| * returns this error message in the HAProxy logs and pop it from the stack. |
| * |
| * This function can fail with an abort() due to an Lua critical error. |
| * We are in the configuration parsing process of HAProxy, this abort() is |
| * tolerated. |
| */ |
| static int hlua_load_state(char **args, lua_State *L, char **err) |
| { |
| int error; |
| int nargs; |
| |
| /* Just load and compile the file. */ |
| error = luaL_loadfile(L, args[0]); |
| if (error) { |
| memprintf(err, "error in Lua file '%s': %s", args[0], lua_tostring(L, -1)); |
| lua_pop(L, 1); |
| return -1; |
| } |
| |
| /* Push args in the Lua stack, except the first one which is the filename */ |
| for (nargs = 1; *(args[nargs]) != 0; nargs++) { |
| /* Check stack size. */ |
| if (!lua_checkstack(L, 1)) { |
| memprintf(err, "Lua runtime error while loading arguments: stack is full."); |
| return -1; |
| } |
| lua_pushstring(L, args[nargs]); |
| } |
| nargs--; |
| |
| /* If no syntax error where detected, execute the code. */ |
| error = lua_pcall(L, nargs, LUA_MULTRET, 0); |
| switch (error) { |
| case LUA_OK: |
| break; |
| case LUA_ERRRUN: |
| memprintf(err, "Lua runtime error: %s", lua_tostring(L, -1)); |
| lua_pop(L, 1); |
| return -1; |
| case LUA_ERRMEM: |
| memprintf(err, "Lua out of memory error"); |
| return -1; |
| case LUA_ERRERR: |
| memprintf(err, "Lua message handler error: %s", lua_tostring(L, -1)); |
| lua_pop(L, 1); |
| return -1; |
| #if defined(LUA_VERSION_NUM) && LUA_VERSION_NUM <= 503 |
| case LUA_ERRGCMM: |
| memprintf(err, "Lua garbage collector error: %s", lua_tostring(L, -1)); |
| lua_pop(L, 1); |
| return -1; |
| #endif |
| default: |
| memprintf(err, "Lua unknown error: %s", lua_tostring(L, -1)); |
| lua_pop(L, 1); |
| return -1; |
| } |
| |
| return 0; |
| } |
| |
| static int hlua_load(char **args, int section_type, struct proxy *curpx, |
| const struct proxy *defpx, const char *file, int line, |
| char **err) |
| { |
| if (*(args[1]) == 0) { |
| memprintf(err, "'%s' expects a file name as parameter.", args[0]); |
| return -1; |
| } |
| |
| /* loading for global state */ |
| hlua_state_id = 0; |
| ha_set_thread(NULL); |
| return hlua_load_state(&args[1], hlua_states[0], err); |
| } |
| |
| static int hlua_load_per_thread(char **args, int section_type, struct proxy *curpx, |
| const struct proxy *defpx, const char *file, int line, |
| char **err) |
| { |
| int len; |
| int i; |
| |
| if (*(args[1]) == 0) { |
| memprintf(err, "'%s' expects a file as parameter.", args[0]); |
| return -1; |
| } |
| |
| if (per_thread_load == NULL) { |
| /* allocate the first entry large enough to store the final NULL */ |
| per_thread_load = calloc(1, sizeof(*per_thread_load)); |
| if (per_thread_load == NULL) { |
| memprintf(err, "out of memory error"); |
| return -1; |
| } |
| } |
| |
| /* count used entries */ |
| for (len = 0; per_thread_load[len] != NULL; len++) |
| ; |
| |
| per_thread_load = realloc(per_thread_load, (len + 2) * sizeof(*per_thread_load)); |
| if (per_thread_load == NULL) { |
| memprintf(err, "out of memory error"); |
| return -1; |
| } |
| per_thread_load[len + 1] = NULL; |
| |
| /* count args excepting the first, allocate array and copy args */ |
| for (i = 0; *(args[i + 1]) != 0; i++); |
| per_thread_load[len] = calloc(i + 1, sizeof(*per_thread_load[len])); |
| if (per_thread_load[len] == NULL) { |
| memprintf(err, "out of memory error"); |
| return -1; |
| } |
| for (i = 1; *(args[i]) != 0; i++) { |
| per_thread_load[len][i - 1] = strdup(args[i]); |
| if (per_thread_load[len][i - 1] == NULL) { |
| memprintf(err, "out of memory error"); |
| return -1; |
| } |
| } |
| per_thread_load[len][i - 1] = strdup(""); |
| if (per_thread_load[len][i - 1] == NULL) { |
| memprintf(err, "out of memory error"); |
| return -1; |
| } |
| |
| /* loading for thread 1 only */ |
| hlua_state_id = 1; |
| ha_set_thread(NULL); |
| return hlua_load_state(per_thread_load[len], hlua_states[1], err); |
| } |
| |
| /* Prepend the given <path> followed by a semicolon to the `package.<type>` variable |
| * in the given <ctx>. |
| */ |
| static int hlua_prepend_path(lua_State *L, char *type, char *path) |
| { |
| lua_getglobal(L, "package"); /* push package variable */ |
| lua_pushstring(L, path); /* push given path */ |
| lua_pushstring(L, ";"); /* push semicolon */ |
| lua_getfield(L, -3, type); /* push old path */ |
| lua_concat(L, 3); /* concatenate to new path */ |
| lua_setfield(L, -2, type); /* store new path */ |
| lua_pop(L, 1); /* pop package variable */ |
| |
| return 0; |
| } |
| |
| static int hlua_config_prepend_path(char **args, int section_type, struct proxy *curpx, |
| const struct proxy *defpx, const char *file, int line, |
| char **err) |
| { |
| char *path; |
| char *type = "path"; |
| struct prepend_path *p = NULL; |
| size_t i; |
| |
| if (too_many_args(2, args, err, NULL)) { |
| goto err; |
| } |
| |
| if (!(*args[1])) { |
| memprintf(err, "'%s' expects to receive a <path> as argument", args[0]); |
| goto err; |
| } |
| path = args[1]; |
| |
| if (*args[2]) { |
| if (strcmp(args[2], "path") != 0 && strcmp(args[2], "cpath") != 0) { |
| memprintf(err, "'%s' expects <type> to either be 'path' or 'cpath'", args[0]); |
| goto err; |
| } |
| type = args[2]; |
| } |
| |
| p = calloc(1, sizeof(*p)); |
| if (p == NULL) { |
| memprintf(err, "memory allocation failed"); |
| goto err; |
| } |
| p->path = strdup(path); |
| if (p->path == NULL) { |
| memprintf(err, "memory allocation failed"); |
| goto err2; |
| } |
| p->type = strdup(type); |
| if (p->type == NULL) { |
| memprintf(err, "memory allocation failed"); |
| goto err2; |
| } |
| LIST_APPEND(&prepend_path_list, &p->l); |
| |
| /* Handle the global state and the per-thread state for the first |
| * thread. The remaining threads will be initialized based on |
| * prepend_path_list. |
| */ |
| for (i = 0; i < 2; i++) { |
| lua_State *L = hlua_states[i]; |
| const char *error; |
| |
| if (setjmp(safe_ljmp_env) != 0) { |
| lua_atpanic(L, hlua_panic_safe); |
| if (lua_type(L, -1) == LUA_TSTRING) |
| error = lua_tostring(L, -1); |
| else |
| error = "critical error"; |
| fprintf(stderr, "lua-prepend-path: %s.\n", error); |
| exit(1); |
| } else { |
| lua_atpanic(L, hlua_panic_ljmp); |
| } |
| |
| hlua_prepend_path(L, type, path); |
| |
| lua_atpanic(L, hlua_panic_safe); |
| } |
| |
| return 0; |
| |
| err2: |
| free(p->type); |
| free(p->path); |
| err: |
| free(p); |
| return -1; |
| } |
| |
| /* configuration keywords declaration */ |
| static struct cfg_kw_list cfg_kws = {{ },{ |
| { CFG_GLOBAL, "lua-prepend-path", hlua_config_prepend_path }, |
| { CFG_GLOBAL, "lua-load", hlua_load }, |
| { CFG_GLOBAL, "lua-load-per-thread", hlua_load_per_thread }, |
| { CFG_GLOBAL, "tune.lua.session-timeout", hlua_session_timeout }, |
| { CFG_GLOBAL, "tune.lua.task-timeout", hlua_task_timeout }, |
| { CFG_GLOBAL, "tune.lua.service-timeout", hlua_applet_timeout }, |
| { CFG_GLOBAL, "tune.lua.burst-timeout", hlua_burst_timeout }, |
| { CFG_GLOBAL, "tune.lua.forced-yield", hlua_forced_yield }, |
| { CFG_GLOBAL, "tune.lua.maxmem", hlua_parse_maxmem }, |
| { 0, NULL, NULL }, |
| }}; |
| |
| INITCALL1(STG_REGISTER, cfg_register_keywords, &cfg_kws); |
| |
| #ifdef USE_OPENSSL |
| |
| /* |
| * This function replace a ckch_store by another one, and rebuild the ckch_inst and all its dependencies. |
| * It does the sam as "cli_io_handler_commit_cert" but for lua, the major |
| * difference is that the yield in lua and for the CLI is not handled the same |
| * way. |
| */ |
| __LJMP static int hlua_ckch_commit_yield(lua_State *L, int status, lua_KContext ctx) |
| { |
| struct ckch_inst **lua_ckchi = lua_touserdata(L, -1); |
| struct ckch_store **lua_ckchs = lua_touserdata(L, -2); |
| struct ckch_inst *ckchi = *lua_ckchi; |
| struct ckch_store *old_ckchs = lua_ckchs[0]; |
| struct ckch_store *new_ckchs = lua_ckchs[1]; |
| struct hlua *hlua; |
| char *err = NULL; |
| int y = 1; |
| |
| hlua = hlua_gethlua(L); |
| |
| /* get the first ckchi to copy */ |
| if (ckchi == NULL) |
| ckchi = LIST_ELEM(old_ckchs->ckch_inst.n, typeof(ckchi), by_ckchs); |
| |
| /* walk through the old ckch_inst and creates new ckch_inst using the updated ckchs */ |
| list_for_each_entry_from(ckchi, &old_ckchs->ckch_inst, by_ckchs) { |
| struct ckch_inst *new_inst; |
| |
| /* it takes a lot of CPU to creates SSL_CTXs, so we yield every 10 CKCH instances */ |
| if (y % 10 == 0) { |
| |
| *lua_ckchi = ckchi; |
| |
| task_wakeup(hlua->task, TASK_WOKEN_MSG); |
| MAY_LJMP(hlua_yieldk(L, 0, 0, hlua_ckch_commit_yield, TICK_ETERNITY, 0)); |
| } |
| |
| if (ckch_inst_rebuild(new_ckchs, ckchi, &new_inst, &err)) |
| goto error; |
| |
| /* link the new ckch_inst to the duplicate */ |
| LIST_APPEND(&new_ckchs->ckch_inst, &new_inst->by_ckchs); |
| y++; |
| } |
| |
| /* The generation is finished, we can insert everything */ |
| ckch_store_replace(old_ckchs, new_ckchs); |
| |
| lua_pop(L, 2); /* pop the lua_ckchs and ckchi */ |
| |
| HA_SPIN_UNLOCK(CKCH_LOCK, &ckch_lock); |
| |
| return 0; |
| |
| error: |
| ckch_store_free(new_ckchs); |
| HA_SPIN_UNLOCK(CKCH_LOCK, &ckch_lock); |
| WILL_LJMP(luaL_error(L, "%s", err)); |
| free(err); |
| |
| return 0; |
| } |
| |
| /* |
| * Replace a ckch_store <filename> in the ckchs_tree with a ckch_store created |
| * from the table in parameter. |
| * |
| * This is equivalent to "set ssl cert" + "commit ssl cert" over the CLI, which |
| * means it does not need to have a transaction since everything is done in the |
| * same function. |
| * |
| * CertCache.set{filename="", crt="", key="", sctl="", ocsp="", issuer=""} |
| * |
| */ |
| __LJMP static int hlua_ckch_set(lua_State *L) |
| { |
| struct hlua *hlua; |
| struct ckch_inst **lua_ckchi; |
| struct ckch_store **lua_ckchs; |
| struct ckch_store *old_ckchs = NULL; |
| struct ckch_store *new_ckchs = NULL; |
| int errcode = 0; |
| char *err = NULL; |
| struct cert_exts *cert_ext = NULL; |
| char *filename; |
| struct ckch_data *data; |
| int ret; |
| |
| if (lua_type(L, -1) != LUA_TTABLE) |
| WILL_LJMP(luaL_error(L, "'CertCache.set' needs a table as argument")); |
| |
| hlua = hlua_gethlua(L); |
| |
| /* FIXME: this should not return an error but should come back later */ |
| if (HA_SPIN_TRYLOCK(CKCH_LOCK, &ckch_lock)) |
| WILL_LJMP(luaL_error(L, "CertCache already under lock")); |
| |
| ret = lua_getfield(L, -1, "filename"); |
| if (ret != LUA_TSTRING) { |
| memprintf(&err, "%sNo filename specified!", err ? err : ""); |
| errcode |= ERR_ALERT | ERR_FATAL; |
| goto end; |
| } |
| filename = (char *)lua_tostring(L, -1); |
| |
| |
| /* look for the filename in the tree */ |
| old_ckchs = ckchs_lookup(filename); |
| if (!old_ckchs) { |
| memprintf(&err, "%sCan't replace a certificate which is not referenced by the configuration!", err ? err : ""); |
| errcode |= ERR_ALERT | ERR_FATAL; |
| goto end; |
| } |
| /* TODO: handle extra_files_noext */ |
| |
| new_ckchs = ckchs_dup(old_ckchs); |
| if (!new_ckchs) { |
| memprintf(&err, "%sCannot allocate memory!", err ? err : ""); |
| errcode |= ERR_ALERT | ERR_FATAL; |
| goto end; |
| } |
| |
| data = new_ckchs->data; |
| |
| /* loop on the field in the table, which have the same name as the |
| * possible extensions of files */ |
| lua_pushnil(L); |
| while (lua_next(L, 1)) { |
| int i; |
| const char *field = lua_tostring(L, -2); |
| char *payload = (char *)lua_tostring(L, -1); |
| |
| if (!field || strcmp(field, "filename") == 0) { |
| lua_pop(L, 1); |
| continue; |
| } |
| |
| for (i = 0; field && cert_exts[i].ext != NULL; i++) { |
| if (strcmp(field, cert_exts[i].ext) == 0) { |
| cert_ext = &cert_exts[i]; |
| break; |
| } |
| } |
| |
| /* this is the default type, the field is not supported */ |
| if (cert_ext == NULL) { |
| memprintf(&err, "%sUnsupported field '%s'", err ? err : "", field); |
| errcode |= ERR_ALERT | ERR_FATAL; |
| goto end; |
| } |
| |
| /* appply the change on the duplicate */ |
| if (cert_ext->load(filename, payload, data, &err) != 0) { |
| memprintf(&err, "%sCan't load the payload for '%s'", err ? err : "", cert_ext->ext); |
| errcode |= ERR_ALERT | ERR_FATAL; |
| goto end; |
| } |
| lua_pop(L, 1); |
| } |
| |
| /* store the pointers on the lua stack */ |
| lua_ckchs = lua_newuserdata(L, sizeof(struct ckch_store *) * 2); |
| lua_ckchs[0] = old_ckchs; |
| lua_ckchs[1] = new_ckchs; |
| lua_ckchi = lua_newuserdata(L, sizeof(struct ckch_inst *)); |
| *lua_ckchi = NULL; |
| |
| task_wakeup(hlua->task, TASK_WOKEN_MSG); |
| MAY_LJMP(hlua_yieldk(L, 0, 0, hlua_ckch_commit_yield, TICK_ETERNITY, 0)); |
| |
| end: |
| HA_SPIN_UNLOCK(CKCH_LOCK, &ckch_lock); |
| |
| if (errcode & ERR_CODE) { |
| ckch_store_free(new_ckchs); |
| WILL_LJMP(luaL_error(L, "%s", err)); |
| } |
| free(err); |
| |
| return 0; |
| } |
| |
| #else |
| |
| __LJMP static int hlua_ckch_set(lua_State *L) |
| { |
| WILL_LJMP(luaL_error(L, "'CertCache.set' needs an HAProxy built with OpenSSL")); |
| |
| return 0; |
| } |
| #endif /* ! USE_OPENSSL */ |
| |
| |
| |
| /* This function can fail with an abort() due to an Lua critical error. |
| * We are in the initialisation process of HAProxy, this abort() is |
| * tolerated. |
| */ |
| int hlua_post_init_state(lua_State *L) |
| { |
| struct hlua_init_function *init; |
| const char *msg; |
| enum hlua_exec ret; |
| const char *error; |
| const char *kind; |
| const char *trace; |
| int return_status = 1; |
| #if defined(LUA_VERSION_NUM) && LUA_VERSION_NUM >= 504 |
| int nres; |
| #endif |
| |
| /* disable memory limit checks if limit is not set */ |
| if (!hlua_global_allocator.limit) |
| hlua_global_allocator.limit = ~hlua_global_allocator.limit; |
| |
| /* Call post initialisation function in safe environment. */ |
| if (setjmp(safe_ljmp_env) != 0) { |
| lua_atpanic(L, hlua_panic_safe); |
| if (lua_type(L, -1) == LUA_TSTRING) |
| error = lua_tostring(L, -1); |
| else |
| error = "critical error"; |
| fprintf(stderr, "Lua post-init: %s.\n", error); |
| exit(1); |
| } else { |
| lua_atpanic(L, hlua_panic_ljmp); |
| } |
| |
| list_for_each_entry(init, &hlua_init_functions[hlua_state_id], l) { |
| hlua_pushref(L, init->function_ref); |
| /* function ref should be released right away since it was pushed |
| * on the stack and will not be used anymore |
| */ |
| hlua_unref(L, init->function_ref); |
| |
| #if defined(LUA_VERSION_NUM) && LUA_VERSION_NUM >= 504 |
| ret = lua_resume(L, L, 0, &nres); |
| #else |
| ret = lua_resume(L, L, 0); |
| #endif |
| kind = NULL; |
| switch (ret) { |
| |
| case LUA_OK: |
| lua_pop(L, -1); |
| break; |
| |
| case LUA_ERRERR: |
| kind = "message handler error"; |
| __fallthrough; |
| case LUA_ERRRUN: |
| if (!kind) |
| kind = "runtime error"; |
| msg = lua_tostring(L, -1); |
| lua_settop(L, 0); /* Empty the stack. */ |
| lua_pop(L, 1); |
| trace = hlua_traceback(L, ", "); |
| if (msg) |
| ha_alert("Lua init: %s: '%s' from %s\n", kind, msg, trace); |
| else |
| ha_alert("Lua init: unknown %s from %s\n", kind, trace); |
| return_status = 0; |
| break; |
| |
| default: |
| /* Unknown error */ |
| kind = "Unknown error"; |
| __fallthrough; |
| case LUA_YIELD: |
| /* yield is not configured at this step, this state doesn't happen */ |
| if (!kind) |
| kind = "yield not allowed"; |
| __fallthrough; |
| case LUA_ERRMEM: |
| if (!kind) |
| kind = "out of memory error"; |
| lua_settop(L, 0); |
| lua_pop(L, 1); |
| trace = hlua_traceback(L, ", "); |
| ha_alert("Lua init: %s: %s\n", kind, trace); |
| return_status = 0; |
| break; |
| } |
| if (!return_status) |
| break; |
| } |
| |
| lua_atpanic(L, hlua_panic_safe); |
| return return_status; |
| } |
| |
| int hlua_post_init() |
| { |
| int ret; |
| int i; |
| int errors; |
| char *err = NULL; |
| struct hlua_function *fcn; |
| struct hlua_reg_filter *reg_flt; |
| |
| #if defined(USE_OPENSSL) |
| /* Initialize SSL server. */ |
| if (socket_ssl->xprt->prepare_srv) { |
| int saved_used_backed = global.ssl_used_backend; |
| // don't affect maxconn automatic computation |
| socket_ssl->xprt->prepare_srv(socket_ssl); |
| global.ssl_used_backend = saved_used_backed; |
| } |
| #endif |
| |
| /* Perform post init of common thread */ |
| hlua_state_id = 0; |
| ha_set_thread(&ha_thread_info[0]); |
| ret = hlua_post_init_state(hlua_states[hlua_state_id]); |
| if (ret == 0) |
| return 0; |
| |
| /* init remaining lua states and load files */ |
| for (hlua_state_id = 2; hlua_state_id < global.nbthread + 1; hlua_state_id++) { |
| |
| /* set thread context */ |
| ha_set_thread(&ha_thread_info[hlua_state_id - 1]); |
| |
| /* Init lua state */ |
| hlua_states[hlua_state_id] = hlua_init_state(hlua_state_id); |
| |
| /* Load lua files */ |
| for (i = 0; per_thread_load && per_thread_load[i]; i++) { |
| ret = hlua_load_state(per_thread_load[i], hlua_states[hlua_state_id], &err); |
| if (ret != 0) { |
| ha_alert("Lua init: %s\n", err); |
| return 0; |
| } |
| } |
| } |
| |
| /* Reset thread context */ |
| ha_set_thread(NULL); |
| |
| /* Execute post init for all states */ |
| for (hlua_state_id = 1; hlua_state_id < global.nbthread + 1; hlua_state_id++) { |
| |
| /* set thread context */ |
| ha_set_thread(&ha_thread_info[hlua_state_id - 1]); |
| |
| /* run post init */ |
| ret = hlua_post_init_state(hlua_states[hlua_state_id]); |
| if (ret == 0) |
| return 0; |
| } |
| |
| /* Reset thread context */ |
| ha_set_thread(NULL); |
| |
| /* control functions registering. Each function must have: |
| * - only the function_ref[0] set positive and all other to -1 |
| * - only the function_ref[0] set to -1 and all other positive |
| * This ensure a same reference is not used both in shared |
| * lua state and thread dedicated lua state. Note: is the case |
| * reach, the shared state is priority, but the bug will be |
| * complicated to found for the end user. |
| */ |
| errors = 0; |
| list_for_each_entry(fcn, &referenced_functions, l) { |
| ret = 0; |
| for (i = 1; i < global.nbthread + 1; i++) { |
| if (fcn->function_ref[i] == -1) |
| ret--; |
| else |
| ret++; |
| } |
| if (abs(ret) != global.nbthread) { |
| ha_alert("Lua function '%s' is not referenced in all thread. " |
| "Expect function in all thread or in none thread.\n", fcn->name); |
| errors++; |
| continue; |
| } |
| |
| if ((fcn->function_ref[0] == -1) == (ret < 0)) { |
| ha_alert("Lua function '%s' is referenced both ins shared Lua context (through lua-load) " |
| "and per-thread Lua context (through lua-load-per-thread). these two context " |
| "exclusive.\n", fcn->name); |
| errors++; |
| } |
| } |
| |
| /* Do the same with registered filters */ |
| list_for_each_entry(reg_flt, &referenced_filters, l) { |
| ret = 0; |
| for (i = 1; i < global.nbthread + 1; i++) { |
| if (reg_flt->flt_ref[i] == -1) |
| ret--; |
| else |
| ret++; |
| } |
| if (abs(ret) != global.nbthread) { |
| ha_alert("Lua filter '%s' is not referenced in all thread. " |
| "Expect function in all thread or in none thread.\n", reg_flt->name); |
| errors++; |
| continue; |
| } |
| |
| if ((reg_flt->flt_ref[0] == -1) == (ret < 0)) { |
| ha_alert("Lua filter '%s' is referenced both ins shared Lua context (through lua-load) " |
| "and per-thread Lua context (through lua-load-per-thread). these two context " |
| "exclusive.\n", fcn->name); |
| errors++; |
| } |
| } |
| |
| |
| if (errors > 0) |
| return 0; |
| |
| /* after this point, this global will no longer be used, so set to |
| * -1 in order to have probably a segfault if someone use it |
| */ |
| hlua_state_id = -1; |
| |
| return 1; |
| } |
| |
| /* The memory allocator used by the Lua stack. <ud> is a pointer to the |
| * allocator's context. <ptr> is the pointer to alloc/free/realloc. <osize> |
| * is the previously allocated size or the kind of object in case of a new |
| * allocation. <nsize> is the requested new size. A new allocation is |
| * indicated by <ptr> being NULL. A free is indicated by <nsize> being |
| * zero. This one verifies that the limits are respected but is optimized |
| * for the fast case where limits are not used, hence stats are not updated. |
| * |
| * Warning: while this API ressembles glibc's realloc() a lot, glibc surpasses |
| * POSIX by making realloc(ptr,0) an effective free(), but others do not do |
| * that and will simply allocate zero as if it were the result of malloc(0), |
| * so mapping this onto realloc() will lead to memory leaks on non-glibc |
| * systems. |
| */ |
| static void *hlua_alloc(void *ud, void *ptr, size_t osize, size_t nsize) |
| { |
| struct hlua_mem_allocator *zone = ud; |
| size_t limit, old, new; |
| |
| /* a limit of ~0 means unlimited and boot complete, so there's no need |
| * for accounting anymore. |
| */ |
| if (likely(~zone->limit == 0)) { |
| if (!nsize) |
| ha_free(&ptr); |
| else |
| ptr = realloc(ptr, nsize); |
| return ptr; |
| } |
| |
| if (!ptr) |
| osize = 0; |
| |
| /* enforce strict limits across all threads */ |
| limit = zone->limit; |
| old = _HA_ATOMIC_LOAD(&zone->allocated); |
| do { |
| new = old + nsize - osize; |
| if (unlikely(nsize && limit && new > limit)) |
| return NULL; |
| } while (!_HA_ATOMIC_CAS(&zone->allocated, &old, new)); |
| |
| if (!nsize) |
| ha_free(&ptr); |
| else |
| ptr = realloc(ptr, nsize); |
| |
| if (unlikely(!ptr && nsize)) // failed |
| _HA_ATOMIC_SUB(&zone->allocated, nsize - osize); |
| |
| __ha_barrier_atomic_store(); |
| return ptr; |
| } |
| |
| /* This function can fail with an abort() due to a Lua critical error. |
| * We are in the initialisation process of HAProxy, this abort() is |
| * tolerated. |
| */ |
| lua_State *hlua_init_state(int thread_num) |
| { |
| int i; |
| int idx; |
| struct sample_fetch *sf; |
| struct sample_conv *sc; |
| char *p; |
| const char *error_msg; |
| void **context; |
| lua_State *L; |
| struct prepend_path *pp; |
| |
| /* Init main lua stack. */ |
| L = lua_newstate(hlua_alloc, &hlua_global_allocator); |
| |
| /* Initialise Lua context to NULL */ |
| context = lua_getextraspace(L); |
| *context = NULL; |
| |
| /* From this point, until the end of the initialisation function, |
| * the Lua function can fail with an abort. We are in the initialisation |
| * process of HAProxy, this abort() is tolerated. |
| */ |
| |
| /* Call post initialisation function in safe environment. */ |
| if (setjmp(safe_ljmp_env) != 0) { |
| lua_atpanic(L, hlua_panic_safe); |
| if (lua_type(L, -1) == LUA_TSTRING) |
| error_msg = lua_tostring(L, -1); |
| else |
| error_msg = "critical error"; |
| fprintf(stderr, "Lua init: %s.\n", error_msg); |
| exit(1); |
| } else { |
| lua_atpanic(L, hlua_panic_ljmp); |
| } |
| |
| /* Initialise lua. */ |
| luaL_openlibs(L); |
| #define HLUA_PREPEND_PATH_TOSTRING1(x) #x |
| #define HLUA_PREPEND_PATH_TOSTRING(x) HLUA_PREPEND_PATH_TOSTRING1(x) |
| #ifdef HLUA_PREPEND_PATH |
| hlua_prepend_path(L, "path", HLUA_PREPEND_PATH_TOSTRING(HLUA_PREPEND_PATH)); |
| #endif |
| #ifdef HLUA_PREPEND_CPATH |
| hlua_prepend_path(L, "cpath", HLUA_PREPEND_PATH_TOSTRING(HLUA_PREPEND_CPATH)); |
| #endif |
| #undef HLUA_PREPEND_PATH_TOSTRING |
| #undef HLUA_PREPEND_PATH_TOSTRING1 |
| |
| /* Apply configured prepend path */ |
| list_for_each_entry(pp, &prepend_path_list, l) |
| hlua_prepend_path(L, pp->type, pp->path); |
| |
| /* |
| * Override some lua functions. |
| * |
| */ |
| |
| /* push our "safe" coroutine.create() function */ |
| lua_getglobal(L, "coroutine"); |
| lua_pushcclosure(L, hlua_coroutine_create, 0); |
| lua_setfield(L, -2, "create"); |
| |
| /* |
| * |
| * Create "core" object. |
| * |
| */ |
| |
| /* This table entry is the object "core" base. */ |
| lua_newtable(L); |
| |
| /* set the thread id */ |
| hlua_class_const_int(L, "thread", thread_num); |
| |
| /* Push the loglevel constants. */ |
| for (i = 0; i < NB_LOG_LEVELS; i++) |
| hlua_class_const_int(L, log_levels[i], i); |
| |
| /* Register special functions. */ |
| hlua_class_function(L, "register_init", hlua_register_init); |
| hlua_class_function(L, "register_task", hlua_register_task); |
| hlua_class_function(L, "register_fetches", hlua_register_fetches); |
| hlua_class_function(L, "register_converters", hlua_register_converters); |
| hlua_class_function(L, "register_action", hlua_register_action); |
| hlua_class_function(L, "register_service", hlua_register_service); |
| hlua_class_function(L, "register_cli", hlua_register_cli); |
| hlua_class_function(L, "register_filter", hlua_register_filter); |
| hlua_class_function(L, "yield", hlua_yield); |
| hlua_class_function(L, "set_nice", hlua_set_nice); |
| hlua_class_function(L, "sleep", hlua_sleep); |
| hlua_class_function(L, "msleep", hlua_msleep); |
| hlua_class_function(L, "add_acl", hlua_add_acl); |
| hlua_class_function(L, "del_acl", hlua_del_acl); |
| hlua_class_function(L, "set_map", hlua_set_map); |
| hlua_class_function(L, "del_map", hlua_del_map); |
| hlua_class_function(L, "tcp", hlua_socket_new); |
| hlua_class_function(L, "httpclient", hlua_httpclient_new); |
| hlua_class_function(L, "event_sub", hlua_event_global_sub); |
| hlua_class_function(L, "log", hlua_log); |
| hlua_class_function(L, "Debug", hlua_log_debug); |
| hlua_class_function(L, "Info", hlua_log_info); |
| hlua_class_function(L, "Warning", hlua_log_warning); |
| hlua_class_function(L, "Alert", hlua_log_alert); |
| hlua_class_function(L, "done", hlua_done); |
| hlua_fcn_reg_core_fcn(L); |
| |
| lua_setglobal(L, "core"); |
| |
| /* |
| * |
| * Create "act" object. |
| * |
| */ |
| |
| /* This table entry is the object "act" base. */ |
| lua_newtable(L); |
| |
| /* push action return constants */ |
| hlua_class_const_int(L, "CONTINUE", ACT_RET_CONT); |
| hlua_class_const_int(L, "STOP", ACT_RET_STOP); |
| hlua_class_const_int(L, "YIELD", ACT_RET_YIELD); |
| hlua_class_const_int(L, "ERROR", ACT_RET_ERR); |
| hlua_class_const_int(L, "DONE", ACT_RET_DONE); |
| hlua_class_const_int(L, "DENY", ACT_RET_DENY); |
| hlua_class_const_int(L, "ABORT", ACT_RET_ABRT); |
| hlua_class_const_int(L, "INVALID", ACT_RET_INV); |
| |
| hlua_class_function(L, "wake_time", hlua_set_wake_time); |
| |
| lua_setglobal(L, "act"); |
| |
| /* |
| * |
| * Create "Filter" object. |
| * |
| */ |
| |
| /* This table entry is the object "filter" base. */ |
| lua_newtable(L); |
| |
| /* push flags and constants */ |
| hlua_class_const_int(L, "CONTINUE", 1); |
| hlua_class_const_int(L, "WAIT", 0); |
| hlua_class_const_int(L, "ERROR", -1); |
| |
| hlua_class_const_int(L, "FLT_CFG_FL_HTX", FLT_CFG_FL_HTX); |
| |
| hlua_class_function(L, "wake_time", hlua_set_wake_time); |
| hlua_class_function(L, "register_data_filter", hlua_register_data_filter); |
| hlua_class_function(L, "unregister_data_filter", hlua_unregister_data_filter); |
| |
| lua_setglobal(L, "filter"); |
| |
| /* |
| * |
| * Register class Map |
| * |
| */ |
| |
| /* This table entry is the object "Map" base. */ |
| lua_newtable(L); |
| |
| /* register pattern types. */ |
| for (i=0; i<PAT_MATCH_NUM; i++) |
| hlua_class_const_int(L, pat_match_names[i], i); |
| for (i=0; i<PAT_MATCH_NUM; i++) { |
| snprintf(trash.area, trash.size, "_%s", pat_match_names[i]); |
| hlua_class_const_int(L, trash.area, i); |
| } |
| |
| /* register constructor. */ |
| hlua_class_function(L, "new", hlua_map_new); |
| |
| /* Create and fill the metatable. */ |
| lua_newtable(L); |
| |
| /* Create and fill the __index entry. */ |
| lua_pushstring(L, "__index"); |
| lua_newtable(L); |
| |
| /* Register . */ |
| hlua_class_function(L, "lookup", hlua_map_lookup); |
| hlua_class_function(L, "slookup", hlua_map_slookup); |
| |
| lua_rawset(L, -3); |
| |
| /* Register previous table in the registry with reference and named entry. |
| * The function hlua_register_metatable() pops the stack, so we |
| * previously create a copy of the table. |
| */ |
| lua_pushvalue(L, -1); /* Copy the -1 entry and push it on the stack. */ |
| class_map_ref = hlua_register_metatable(L, CLASS_MAP); |
| |
| /* Assign the metatable to the mai Map object. */ |
| lua_setmetatable(L, -2); |
| |
| /* Set a name to the table. */ |
| lua_setglobal(L, "Map"); |
| |
| /* |
| * |
| * Register "CertCache" class |
| * |
| */ |
| |
| /* Create and fill the metatable. */ |
| lua_newtable(L); |
| /* Register */ |
| hlua_class_function(L, "set", hlua_ckch_set); |
| lua_setglobal(L, CLASS_CERTCACHE); /* Create global object called Regex */ |
| |
| /* |
| * |
| * Register class Channel |
| * |
| */ |
| |
| /* Create and fill the metatable. */ |
| lua_newtable(L); |
| |
| /* Create and fill the __index entry. */ |
| lua_pushstring(L, "__index"); |
| lua_newtable(L); |
| |
| /* Register . */ |
| hlua_class_function(L, "data", hlua_channel_get_data); |
| hlua_class_function(L, "line", hlua_channel_get_line); |
| hlua_class_function(L, "set", hlua_channel_set_data); |
| hlua_class_function(L, "remove", hlua_channel_del_data); |
| hlua_class_function(L, "append", hlua_channel_append); |
| hlua_class_function(L, "prepend", hlua_channel_prepend); |
| hlua_class_function(L, "insert", hlua_channel_insert_data); |
| hlua_class_function(L, "send", hlua_channel_send); |
| hlua_class_function(L, "forward", hlua_channel_forward); |
| hlua_class_function(L, "input", hlua_channel_get_in_len); |
| hlua_class_function(L, "output", hlua_channel_get_out_len); |
| hlua_class_function(L, "may_recv", hlua_channel_may_recv); |
| hlua_class_function(L, "is_full", hlua_channel_is_full); |
| hlua_class_function(L, "is_resp", hlua_channel_is_resp); |
| |
| /* Deprecated API */ |
| hlua_class_function(L, "get", hlua_channel_get); |
| hlua_class_function(L, "dup", hlua_channel_dup); |
| hlua_class_function(L, "getline", hlua_channel_getline); |
| hlua_class_function(L, "get_in_len", hlua_channel_get_in_len); |
| hlua_class_function(L, "get_out_len", hlua_channel_get_out_len); |
| |
| lua_rawset(L, -3); |
| |
| /* Register previous table in the registry with reference and named entry. */ |
| class_channel_ref = hlua_register_metatable(L, CLASS_CHANNEL); |
| |
| /* |
| * |
| * Register class Fetches |
| * |
| */ |
| |
| /* Create and fill the metatable. */ |
| lua_newtable(L); |
| |
| /* Create and fill the __index entry. */ |
| lua_pushstring(L, "__index"); |
| lua_newtable(L); |
| |
| /* Browse existing fetches and create the associated |
| * object method. |
| */ |
| sf = NULL; |
| while ((sf = sample_fetch_getnext(sf, &idx)) != NULL) { |
| /* gL.Tua doesn't support '.' and '-' in the function names, replace it |
| * by an underscore. |
| */ |
| strlcpy2(trash.area, sf->kw, trash.size); |
| for (p = trash.area; *p; p++) |
| if (*p == '.' || *p == '-' || *p == '+') |
| *p = '_'; |
| |
| /* Register the function. */ |
| lua_pushstring(L, trash.area); |
| lua_pushlightuserdata(L, sf); |
| lua_pushcclosure(L, hlua_run_sample_fetch, 1); |
| lua_rawset(L, -3); |
| } |
| |
| lua_rawset(L, -3); |
| |
| /* Register previous table in the registry with reference and named entry. */ |
| class_fetches_ref = hlua_register_metatable(L, CLASS_FETCHES); |
| |
| /* |
| * |
| * Register class Converters |
| * |
| */ |
| |
| /* Create and fill the metatable. */ |
| lua_newtable(L); |
| |
| /* Create and fill the __index entry. */ |
| lua_pushstring(L, "__index"); |
| lua_newtable(L); |
| |
| /* Browse existing converters and create the associated |
| * object method. |
| */ |
| sc = NULL; |
| while ((sc = sample_conv_getnext(sc, &idx)) != NULL) { |
| /* gL.Tua doesn't support '.' and '-' in the function names, replace it |
| * by an underscore. |
| */ |
| strlcpy2(trash.area, sc->kw, trash.size); |
| for (p = trash.area; *p; p++) |
| if (*p == '.' || *p == '-' || *p == '+') |
| *p = '_'; |
| |
| /* Register the function. */ |
| lua_pushstring(L, trash.area); |
| lua_pushlightuserdata(L, sc); |
| lua_pushcclosure(L, hlua_run_sample_conv, 1); |
| lua_rawset(L, -3); |
| } |
| |
| lua_rawset(L, -3); |
| |
| /* Register previous table in the registry with reference and named entry. */ |
| class_converters_ref = hlua_register_metatable(L, CLASS_CONVERTERS); |
| |
| /* |
| * |
| * Register class HTTP |
| * |
| */ |
| |
| /* Create and fill the metatable. */ |
| lua_newtable(L); |
| |
| /* Create and fill the __index entry. */ |
| lua_pushstring(L, "__index"); |
| lua_newtable(L); |
| |
| /* Register Lua functions. */ |
| hlua_class_function(L, "req_get_headers",hlua_http_req_get_headers); |
| hlua_class_function(L, "req_del_header", hlua_http_req_del_hdr); |
| hlua_class_function(L, "req_rep_header", hlua_http_req_rep_hdr); |
| hlua_class_function(L, "req_rep_value", hlua_http_req_rep_val); |
| hlua_class_function(L, "req_add_header", hlua_http_req_add_hdr); |
| hlua_class_function(L, "req_set_header", hlua_http_req_set_hdr); |
| hlua_class_function(L, "req_set_method", hlua_http_req_set_meth); |
| hlua_class_function(L, "req_set_path", hlua_http_req_set_path); |
| hlua_class_function(L, "req_set_query", hlua_http_req_set_query); |
| hlua_class_function(L, "req_set_uri", hlua_http_req_set_uri); |
| |
| hlua_class_function(L, "res_get_headers",hlua_http_res_get_headers); |
| hlua_class_function(L, "res_del_header", hlua_http_res_del_hdr); |
| hlua_class_function(L, "res_rep_header", hlua_http_res_rep_hdr); |
| hlua_class_function(L, "res_rep_value", hlua_http_res_rep_val); |
| hlua_class_function(L, "res_add_header", hlua_http_res_add_hdr); |
| hlua_class_function(L, "res_set_header", hlua_http_res_set_hdr); |
| hlua_class_function(L, "res_set_status", hlua_http_res_set_status); |
| |
| lua_rawset(L, -3); |
| |
| /* Register previous table in the registry with reference and named entry. */ |
| class_http_ref = hlua_register_metatable(L, CLASS_HTTP); |
| |
| /* |
| * |
| * Register class HTTPMessage |
| * |
| */ |
| |
| /* Create and fill the metatable. */ |
| lua_newtable(L); |
| |
| /* Create and fill the __index entry. */ |
| lua_pushstring(L, "__index"); |
| lua_newtable(L); |
| |
| /* Register Lua functions. */ |
| hlua_class_function(L, "is_resp", hlua_http_msg_is_resp); |
| hlua_class_function(L, "get_stline", hlua_http_msg_get_stline); |
| hlua_class_function(L, "get_headers", hlua_http_msg_get_headers); |
| hlua_class_function(L, "del_header", hlua_http_msg_del_hdr); |
| hlua_class_function(L, "rep_header", hlua_http_msg_rep_hdr); |
| hlua_class_function(L, "rep_value", hlua_http_msg_rep_val); |
| hlua_class_function(L, "add_header", hlua_http_msg_add_hdr); |
| hlua_class_function(L, "set_header", hlua_http_msg_set_hdr); |
| hlua_class_function(L, "set_method", hlua_http_msg_set_meth); |
| hlua_class_function(L, "set_path", hlua_http_msg_set_path); |
| hlua_class_function(L, "set_query", hlua_http_msg_set_query); |
| hlua_class_function(L, "set_uri", hlua_http_msg_set_uri); |
| hlua_class_function(L, "set_status", hlua_http_msg_set_status); |
| hlua_class_function(L, "is_full", hlua_http_msg_is_full); |
| hlua_class_function(L, "may_recv", hlua_http_msg_may_recv); |
| hlua_class_function(L, "eom", hlua_http_msg_is_eom); |
| hlua_class_function(L, "input", hlua_http_msg_get_in_len); |
| hlua_class_function(L, "output", hlua_http_msg_get_out_len); |
| |
| hlua_class_function(L, "body", hlua_http_msg_get_body); |
| hlua_class_function(L, "set", hlua_http_msg_set_data); |
| hlua_class_function(L, "remove", hlua_http_msg_del_data); |
| hlua_class_function(L, "append", hlua_http_msg_append); |
| hlua_class_function(L, "prepend", hlua_http_msg_prepend); |
| hlua_class_function(L, "insert", hlua_http_msg_insert_data); |
| hlua_class_function(L, "set_eom", hlua_http_msg_set_eom); |
| hlua_class_function(L, "unset_eom", hlua_http_msg_unset_eom); |
| |
| hlua_class_function(L, "send", hlua_http_msg_send); |
| hlua_class_function(L, "forward", hlua_http_msg_forward); |
| |
| lua_rawset(L, -3); |
| |
| /* Register previous table in the registry with reference and named entry. */ |
| class_http_msg_ref = hlua_register_metatable(L, CLASS_HTTP_MSG); |
| |
| /* |
| * |
| * Register class HTTPClient |
| * |
| */ |
| |
| /* Create and fill the metatable. */ |
| lua_newtable(L); |
| lua_pushstring(L, "__index"); |
| lua_newtable(L); |
| hlua_class_function(L, "get", hlua_httpclient_get); |
| hlua_class_function(L, "head", hlua_httpclient_head); |
| hlua_class_function(L, "put", hlua_httpclient_put); |
| hlua_class_function(L, "post", hlua_httpclient_post); |
| hlua_class_function(L, "delete", hlua_httpclient_delete); |
| lua_settable(L, -3); /* Sets the __index entry. */ |
| /* Register the garbage collector entry. */ |
| lua_pushstring(L, "__gc"); |
| lua_pushcclosure(L, hlua_httpclient_gc, 0); |
| lua_settable(L, -3); /* Push the last 2 entries in the table at index -3 */ |
| |
| |
| |
| class_httpclient_ref = hlua_register_metatable(L, CLASS_HTTPCLIENT); |
| /* |
| * |
| * Register class AppletTCP |
| * |
| */ |
| |
| /* Create and fill the metatable. */ |
| lua_newtable(L); |
| |
| /* Create and fill the __index entry. */ |
| lua_pushstring(L, "__index"); |
| lua_newtable(L); |
| |
| /* Register Lua functions. */ |
| hlua_class_function(L, "getline", hlua_applet_tcp_getline); |
| hlua_class_function(L, "receive", hlua_applet_tcp_recv); |
| hlua_class_function(L, "send", hlua_applet_tcp_send); |
| hlua_class_function(L, "set_priv", hlua_applet_tcp_set_priv); |
| hlua_class_function(L, "get_priv", hlua_applet_tcp_get_priv); |
| hlua_class_function(L, "set_var", hlua_applet_tcp_set_var); |
| hlua_class_function(L, "unset_var", hlua_applet_tcp_unset_var); |
| hlua_class_function(L, "get_var", hlua_applet_tcp_get_var); |
| |
| lua_settable(L, -3); |
| |
| /* Register previous table in the registry with reference and named entry. */ |
| class_applet_tcp_ref = hlua_register_metatable(L, CLASS_APPLET_TCP); |
| |
| /* |
| * |
| * Register class AppletHTTP |
| * |
| */ |
| |
| /* Create and fill the metatable. */ |
| lua_newtable(L); |
| |
| /* Create and fill the __index entry. */ |
| lua_pushstring(L, "__index"); |
| lua_newtable(L); |
| |
| /* Register Lua functions. */ |
| hlua_class_function(L, "set_priv", hlua_applet_http_set_priv); |
| hlua_class_function(L, "get_priv", hlua_applet_http_get_priv); |
| hlua_class_function(L, "set_var", hlua_applet_http_set_var); |
| hlua_class_function(L, "unset_var", hlua_applet_http_unset_var); |
| hlua_class_function(L, "get_var", hlua_applet_http_get_var); |
| hlua_class_function(L, "getline", hlua_applet_http_getline); |
| hlua_class_function(L, "receive", hlua_applet_http_recv); |
| hlua_class_function(L, "send", hlua_applet_http_send); |
| hlua_class_function(L, "add_header", hlua_applet_http_addheader); |
| hlua_class_function(L, "set_status", hlua_applet_http_status); |
| hlua_class_function(L, "start_response", hlua_applet_http_start_response); |
| |
| lua_settable(L, -3); |
| |
| /* Register previous table in the registry with reference and named entry. */ |
| class_applet_http_ref = hlua_register_metatable(L, CLASS_APPLET_HTTP); |
| |
| /* |
| * |
| * Register class TXN |
| * |
| */ |
| |
| /* Create and fill the metatable. */ |
| lua_newtable(L); |
| |
| /* Create and fill the __index entry. */ |
| lua_pushstring(L, "__index"); |
| lua_newtable(L); |
| |
| /* Register Lua functions. */ |
| hlua_class_function(L, "set_priv", hlua_set_priv); |
| hlua_class_function(L, "get_priv", hlua_get_priv); |
| hlua_class_function(L, "set_var", hlua_set_var); |
| hlua_class_function(L, "unset_var", hlua_unset_var); |
| hlua_class_function(L, "get_var", hlua_get_var); |
| hlua_class_function(L, "done", hlua_txn_done); |
| hlua_class_function(L, "reply", hlua_txn_reply_new); |
| hlua_class_function(L, "set_loglevel", hlua_txn_set_loglevel); |
| hlua_class_function(L, "set_tos", hlua_txn_set_tos); |
| hlua_class_function(L, "set_mark", hlua_txn_set_mark); |
| hlua_class_function(L, "set_priority_class", hlua_txn_set_priority_class); |
| hlua_class_function(L, "set_priority_offset", hlua_txn_set_priority_offset); |
| hlua_class_function(L, "deflog", hlua_txn_deflog); |
| hlua_class_function(L, "log", hlua_txn_log); |
| hlua_class_function(L, "Debug", hlua_txn_log_debug); |
| hlua_class_function(L, "Info", hlua_txn_log_info); |
| hlua_class_function(L, "Warning", hlua_txn_log_warning); |
| hlua_class_function(L, "Alert", hlua_txn_log_alert); |
| |
| lua_rawset(L, -3); |
| |
| /* Register previous table in the registry with reference and named entry. */ |
| class_txn_ref = hlua_register_metatable(L, CLASS_TXN); |
| |
| /* |
| * |
| * Register class reply |
| * |
| */ |
| lua_newtable(L); |
| lua_pushstring(L, "__index"); |
| lua_newtable(L); |
| hlua_class_function(L, "set_status", hlua_txn_reply_set_status); |
| hlua_class_function(L, "add_header", hlua_txn_reply_add_header); |
| hlua_class_function(L, "del_header", hlua_txn_reply_del_header); |
| hlua_class_function(L, "set_body", hlua_txn_reply_set_body); |
| lua_settable(L, -3); /* Sets the __index entry. */ |
| class_txn_reply_ref = luaL_ref(L, LUA_REGISTRYINDEX); |
| |
| |
| /* |
| * |
| * Register class Socket |
| * |
| */ |
| |
| /* Create and fill the metatable. */ |
| lua_newtable(L); |
| |
| /* Create and fill the __index entry. */ |
| lua_pushstring(L, "__index"); |
| lua_newtable(L); |
| |
| #ifdef USE_OPENSSL |
| hlua_class_function(L, "connect_ssl", hlua_socket_connect_ssl); |
| #endif |
| hlua_class_function(L, "connect", hlua_socket_connect); |
| hlua_class_function(L, "send", hlua_socket_send); |
| hlua_class_function(L, "receive", hlua_socket_receive); |
| hlua_class_function(L, "close", hlua_socket_close); |
| hlua_class_function(L, "getpeername", hlua_socket_getpeername); |
| hlua_class_function(L, "getsockname", hlua_socket_getsockname); |
| hlua_class_function(L, "setoption", hlua_socket_setoption); |
| hlua_class_function(L, "settimeout", hlua_socket_settimeout); |
| |
| lua_rawset(L, -3); /* Push the last 2 entries in the table at index -3 */ |
| |
| /* Register the garbage collector entry. */ |
| lua_pushstring(L, "__gc"); |
| lua_pushcclosure(L, hlua_socket_gc, 0); |
| lua_rawset(L, -3); /* Push the last 2 entries in the table at index -3 */ |
| |
| /* Register previous table in the registry with reference and named entry. */ |
| class_socket_ref = hlua_register_metatable(L, CLASS_SOCKET); |
| |
| lua_atpanic(L, hlua_panic_safe); |
| |
| return L; |
| } |
| |
| void hlua_init(void) { |
| int i; |
| char *errmsg; |
| #ifdef USE_OPENSSL |
| struct srv_kw *kw; |
| int tmp_error; |
| char *error; |
| char *args[] = { /* SSL client configuration. */ |
| "ssl", |
| "verify", |
| "none", |
| NULL |
| }; |
| #endif |
| |
| /* Init post init function list head */ |
| for (i = 0; i < MAX_THREADS + 1; i++) |
| LIST_INIT(&hlua_init_functions[i]); |
| |
| /* Init state for common/shared lua parts */ |
| hlua_state_id = 0; |
| ha_set_thread(NULL); |
| hlua_states[0] = hlua_init_state(0); |
| |
| /* Init state 1 for thread 0. We have at least one thread. */ |
| hlua_state_id = 1; |
| ha_set_thread(NULL); |
| hlua_states[1] = hlua_init_state(1); |
| |
| /* Proxy and server configuration initialisation. */ |
| socket_proxy = alloc_new_proxy("LUA-SOCKET", PR_CAP_FE|PR_CAP_BE|PR_CAP_INT, &errmsg); |
| if (!socket_proxy) { |
| fprintf(stderr, "Lua init: %s\n", errmsg); |
| exit(1); |
| } |
| proxy_preset_defaults(socket_proxy); |
| |
| /* Init TCP server: unchanged parameters */ |
| socket_tcp = new_server(socket_proxy); |
| if (!socket_tcp) { |
| fprintf(stderr, "Lua init: failed to allocate tcp server socket\n"); |
| exit(1); |
| } |
| |
| #ifdef USE_OPENSSL |
| /* Init TCP server: unchanged parameters */ |
| socket_ssl = new_server(socket_proxy); |
| if (!socket_ssl) { |
| fprintf(stderr, "Lua init: failed to allocate ssl server socket\n"); |
| exit(1); |
| } |
| |
| socket_ssl->use_ssl = 1; |
| socket_ssl->xprt = xprt_get(XPRT_SSL); |
| |
| for (i = 0; args[i] != NULL; i++) { |
| if ((kw = srv_find_kw(args[i])) != NULL) { /* Maybe it's registered server keyword */ |
| /* |
| * |
| * If the keyword is not known, we can search in the registered |
| * server keywords. This is useful to configure special SSL |
| * features like client certificates and ssl_verify. |
| * |
| */ |
| tmp_error = kw->parse(args, &i, socket_proxy, socket_ssl, &error); |
| if (tmp_error != 0) { |
| fprintf(stderr, "INTERNAL ERROR: %s\n", error); |
| abort(); /* This must be never arrives because the command line |
| not editable by the user. */ |
| } |
| i += kw->skip; |
| } |
| } |
| #endif |
| |
| } |
| |
| static void hlua_deinit() |
| { |
| int thr; |
| struct hlua_reg_filter *reg_flt, *reg_flt_bck; |
| |
| list_for_each_entry_safe(reg_flt, reg_flt_bck, &referenced_filters, l) |
| release_hlua_reg_filter(reg_flt); |
| |
| for (thr = 0; thr < MAX_THREADS+1; thr++) { |
| if (hlua_states[thr]) |
| lua_close(hlua_states[thr]); |
| } |
| |
| srv_drop(socket_tcp); |
| |
| #ifdef USE_OPENSSL |
| srv_drop(socket_ssl); |
| #endif |
| |
| free_proxy(socket_proxy); |
| } |
| |
| REGISTER_POST_DEINIT(hlua_deinit); |
| |
| static void hlua_register_build_options(void) |
| { |
| char *ptr = NULL; |
| |
| memprintf(&ptr, "Built with Lua version : %s", LUA_RELEASE); |
| hap_register_build_opts(ptr, 1); |
| } |
| |
| INITCALL0(STG_REGISTER, hlua_register_build_options); |