1 | /* |
2 | * Copyright (c) 2009-2012, Salvatore Sanfilippo <antirez at gmail dot com> |
3 | * All rights reserved. |
4 | * |
5 | * Redistribution and use in source and binary forms, with or without |
6 | * modification, are permitted provided that the following conditions are met: |
7 | * |
8 | * * Redistributions of source code must retain the above copyright notice, |
9 | * this list of conditions and the following disclaimer. |
10 | * * Redistributions in binary form must reproduce the above copyright |
11 | * notice, this list of conditions and the following disclaimer in the |
12 | * documentation and/or other materials provided with the distribution. |
13 | * * Neither the name of Redis nor the names of its contributors may be used |
14 | * to endorse or promote products derived from this software without |
15 | * specific prior written permission. |
16 | * |
17 | * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" |
18 | * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE |
19 | * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE |
20 | * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE |
21 | * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR |
22 | * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF |
23 | * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS |
24 | * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN |
25 | * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) |
26 | * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE |
27 | * POSSIBILITY OF SUCH DAMAGE. |
28 | */ |
29 | |
30 | #include "server.h" |
31 | #include "sha1.h" |
32 | #include "rand.h" |
33 | #include "cluster.h" |
34 | #include "monotonic.h" |
35 | #include "resp_parser.h" |
36 | #include "script_lua.h" |
37 | |
38 | #include <lua.h> |
39 | #include <lauxlib.h> |
40 | #include <lualib.h> |
41 | #include <ctype.h> |
42 | #include <math.h> |
43 | |
44 | void ldbInit(void); |
45 | void ldbDisable(client *c); |
46 | void ldbEnable(client *c); |
47 | void evalGenericCommandWithDebugging(client *c, int evalsha); |
48 | sds ldbCatStackValue(sds s, lua_State *lua, int idx); |
49 | |
50 | static void dictLuaScriptDestructor(dict *d, void *val) { |
51 | UNUSED(d); |
52 | if (val == NULL) return; /* Lazy freeing will set value to NULL. */ |
53 | decrRefCount(((luaScript*)val)->body); |
54 | zfree(val); |
55 | } |
56 | |
57 | static uint64_t dictStrCaseHash(const void *key) { |
58 | return dictGenCaseHashFunction((unsigned char*)key, strlen((char*)key)); |
59 | } |
60 | |
61 | /* server.lua_scripts sha (as sds string) -> scripts (as luaScript) cache. */ |
62 | dictType shaScriptObjectDictType = { |
63 | dictStrCaseHash, /* hash function */ |
64 | NULL, /* key dup */ |
65 | NULL, /* val dup */ |
66 | dictSdsKeyCaseCompare, /* key compare */ |
67 | dictSdsDestructor, /* key destructor */ |
68 | dictLuaScriptDestructor, /* val destructor */ |
69 | NULL /* allow to expand */ |
70 | }; |
71 | |
72 | /* Lua context */ |
73 | struct luaCtx { |
74 | lua_State *lua; /* The Lua interpreter. We use just one for all clients */ |
75 | client *lua_client; /* The "fake client" to query Redis from Lua */ |
76 | dict *lua_scripts; /* A dictionary of SHA1 -> Lua scripts */ |
77 | unsigned long long lua_scripts_mem; /* Cached scripts' memory + oh */ |
78 | } lctx; |
79 | |
80 | /* Debugger shared state is stored inside this global structure. */ |
81 | #define LDB_BREAKPOINTS_MAX 64 /* Max number of breakpoints. */ |
82 | #define LDB_MAX_LEN_DEFAULT 256 /* Default len limit for replies / var dumps. */ |
83 | struct ldbState { |
84 | connection *conn; /* Connection of the debugging client. */ |
85 | int active; /* Are we debugging EVAL right now? */ |
86 | int forked; /* Is this a fork()ed debugging session? */ |
87 | list *logs; /* List of messages to send to the client. */ |
88 | list *traces; /* Messages about Redis commands executed since last stop.*/ |
89 | list *children; /* All forked debugging sessions pids. */ |
90 | int bp[LDB_BREAKPOINTS_MAX]; /* An array of breakpoints line numbers. */ |
91 | int bpcount; /* Number of valid entries inside bp. */ |
92 | int step; /* Stop at next line regardless of breakpoints. */ |
93 | int luabp; /* Stop at next line because redis.breakpoint() was called. */ |
94 | sds *src; /* Lua script source code split by line. */ |
95 | int lines; /* Number of lines in 'src'. */ |
96 | int currentline; /* Current line number. */ |
97 | sds cbuf; /* Debugger client command buffer. */ |
98 | size_t maxlen; /* Max var dump / reply length. */ |
99 | int maxlen_hint_sent; /* Did we already hint about "set maxlen"? */ |
100 | } ldb; |
101 | |
102 | /* --------------------------------------------------------------------------- |
103 | * Utility functions. |
104 | * ------------------------------------------------------------------------- */ |
105 | |
106 | /* Perform the SHA1 of the input string. We use this both for hashing script |
107 | * bodies in order to obtain the Lua function name, and in the implementation |
108 | * of redis.sha1(). |
109 | * |
110 | * 'digest' should point to a 41 bytes buffer: 40 for SHA1 converted into an |
111 | * hexadecimal number, plus 1 byte for null term. */ |
112 | void sha1hex(char *digest, char *script, size_t len) { |
113 | SHA1_CTX ctx; |
114 | unsigned char hash[20]; |
115 | char *cset = "0123456789abcdef" ; |
116 | int j; |
117 | |
118 | SHA1Init(&ctx); |
119 | SHA1Update(&ctx,(unsigned char*)script,len); |
120 | SHA1Final(hash,&ctx); |
121 | |
122 | for (j = 0; j < 20; j++) { |
123 | digest[j*2] = cset[((hash[j]&0xF0)>>4)]; |
124 | digest[j*2+1] = cset[(hash[j]&0xF)]; |
125 | } |
126 | digest[40] = '\0'; |
127 | } |
128 | |
129 | /* redis.breakpoint() |
130 | * |
131 | * Allows to stop execution during a debugging session from within |
132 | * the Lua code implementation, like if a breakpoint was set in the code |
133 | * immediately after the function. */ |
134 | int luaRedisBreakpointCommand(lua_State *lua) { |
135 | if (ldb.active) { |
136 | ldb.luabp = 1; |
137 | lua_pushboolean(lua,1); |
138 | } else { |
139 | lua_pushboolean(lua,0); |
140 | } |
141 | return 1; |
142 | } |
143 | |
144 | /* redis.debug() |
145 | * |
146 | * Log a string message into the output console. |
147 | * Can take multiple arguments that will be separated by commas. |
148 | * Nothing is returned to the caller. */ |
149 | int luaRedisDebugCommand(lua_State *lua) { |
150 | if (!ldb.active) return 0; |
151 | int argc = lua_gettop(lua); |
152 | sds log = sdscatprintf(sdsempty(),"<debug> line %d: " , ldb.currentline); |
153 | while(argc--) { |
154 | log = ldbCatStackValue(log,lua,-1 - argc); |
155 | if (argc != 0) log = sdscatlen(log,", " ,2); |
156 | } |
157 | ldbLog(log); |
158 | return 0; |
159 | } |
160 | |
161 | /* redis.replicate_commands() |
162 | * |
163 | * DEPRECATED: Now do nothing and always return true. |
164 | * Turn on single commands replication if the script never called |
165 | * a write command so far, and returns true. Otherwise if the script |
166 | * already started to write, returns false and stick to whole scripts |
167 | * replication, which is our default. */ |
168 | int luaRedisReplicateCommandsCommand(lua_State *lua) { |
169 | lua_pushboolean(lua,1); |
170 | return 1; |
171 | } |
172 | |
173 | /* Initialize the scripting environment. |
174 | * |
175 | * This function is called the first time at server startup with |
176 | * the 'setup' argument set to 1. |
177 | * |
178 | * It can be called again multiple times during the lifetime of the Redis |
179 | * process, with 'setup' set to 0, and following a scriptingRelease() call, |
180 | * in order to reset the Lua scripting environment. |
181 | * |
182 | * However it is simpler to just call scriptingReset() that does just that. */ |
183 | void scriptingInit(int setup) { |
184 | lua_State *lua = lua_open(); |
185 | |
186 | if (setup) { |
187 | lctx.lua_client = NULL; |
188 | server.script_caller = NULL; |
189 | server.script_disable_deny_script = 0; |
190 | ldbInit(); |
191 | } |
192 | |
193 | /* Initialize a dictionary we use to map SHAs to scripts. |
194 | * This is useful for replication, as we need to replicate EVALSHA |
195 | * as EVAL, so we need to remember the associated script. */ |
196 | lctx.lua_scripts = dictCreate(&shaScriptObjectDictType); |
197 | lctx.lua_scripts_mem = 0; |
198 | |
199 | luaRegisterRedisAPI(lua); |
200 | |
201 | /* register debug commands */ |
202 | lua_getglobal(lua,"redis" ); |
203 | |
204 | /* redis.breakpoint */ |
205 | lua_pushstring(lua,"breakpoint" ); |
206 | lua_pushcfunction(lua,luaRedisBreakpointCommand); |
207 | lua_settable(lua,-3); |
208 | |
209 | /* redis.debug */ |
210 | lua_pushstring(lua,"debug" ); |
211 | lua_pushcfunction(lua,luaRedisDebugCommand); |
212 | lua_settable(lua,-3); |
213 | |
214 | /* redis.replicate_commands */ |
215 | lua_pushstring(lua, "replicate_commands" ); |
216 | lua_pushcfunction(lua, luaRedisReplicateCommandsCommand); |
217 | lua_settable(lua, -3); |
218 | |
219 | lua_setglobal(lua,"redis" ); |
220 | |
221 | /* Add a helper function we use for pcall error reporting. |
222 | * Note that when the error is in the C function we want to report the |
223 | * information about the caller, that's what makes sense from the point |
224 | * of view of the user debugging a script. */ |
225 | { |
226 | char *errh_func = "local dbg = debug\n" |
227 | "debug = nil\n" |
228 | "function __redis__err__handler(err)\n" |
229 | " local i = dbg.getinfo(2,'nSl')\n" |
230 | " if i and i.what == 'C' then\n" |
231 | " i = dbg.getinfo(3,'nSl')\n" |
232 | " end\n" |
233 | " if type(err) ~= 'table' then\n" |
234 | " err = {err='ERR ' .. tostring(err)}" |
235 | " end" |
236 | " if i then\n" |
237 | " err['source'] = i.source\n" |
238 | " err['line'] = i.currentline\n" |
239 | " end" |
240 | " return err\n" |
241 | "end\n" ; |
242 | luaL_loadbuffer(lua,errh_func,strlen(errh_func),"@err_handler_def" ); |
243 | lua_pcall(lua,0,0,0); |
244 | } |
245 | |
246 | /* Create the (non connected) client that we use to execute Redis commands |
247 | * inside the Lua interpreter. |
248 | * Note: there is no need to create it again when this function is called |
249 | * by scriptingReset(). */ |
250 | if (lctx.lua_client == NULL) { |
251 | lctx.lua_client = createClient(NULL); |
252 | lctx.lua_client->flags |= CLIENT_SCRIPT; |
253 | |
254 | /* We do not want to allow blocking commands inside Lua */ |
255 | lctx.lua_client->flags |= CLIENT_DENY_BLOCKING; |
256 | } |
257 | |
258 | /* Lock the global table from any changes */ |
259 | lua_pushvalue(lua, LUA_GLOBALSINDEX); |
260 | luaSetErrorMetatable(lua); |
261 | /* Recursively lock all tables that can be reached from the global table */ |
262 | luaSetTableProtectionRecursively(lua); |
263 | lua_pop(lua, 1); |
264 | |
265 | lctx.lua = lua; |
266 | } |
267 | |
268 | /* Release resources related to Lua scripting. |
269 | * This function is used in order to reset the scripting environment. */ |
270 | void scriptingRelease(int async) { |
271 | if (async) |
272 | freeLuaScriptsAsync(lctx.lua_scripts); |
273 | else |
274 | dictRelease(lctx.lua_scripts); |
275 | lctx.lua_scripts_mem = 0; |
276 | lua_close(lctx.lua); |
277 | } |
278 | |
279 | void scriptingReset(int async) { |
280 | scriptingRelease(async); |
281 | scriptingInit(0); |
282 | } |
283 | |
284 | /* --------------------------------------------------------------------------- |
285 | * EVAL and SCRIPT commands implementation |
286 | * ------------------------------------------------------------------------- */ |
287 | |
288 | static void evalCalcFunctionName(int evalsha, sds script, char *out_funcname) { |
289 | /* We obtain the script SHA1, then check if this function is already |
290 | * defined into the Lua state */ |
291 | out_funcname[0] = 'f'; |
292 | out_funcname[1] = '_'; |
293 | if (!evalsha) { |
294 | /* Hash the code if this is an EVAL call */ |
295 | sha1hex(out_funcname+2,script,sdslen(script)); |
296 | } else { |
297 | /* We already have the SHA if it is an EVALSHA */ |
298 | int j; |
299 | char *sha = script; |
300 | |
301 | /* Convert to lowercase. We don't use tolower since the function |
302 | * managed to always show up in the profiler output consuming |
303 | * a non trivial amount of time. */ |
304 | for (j = 0; j < 40; j++) |
305 | out_funcname[j+2] = (sha[j] >= 'A' && sha[j] <= 'Z') ? |
306 | sha[j]+('a'-'A') : sha[j]; |
307 | out_funcname[42] = '\0'; |
308 | } |
309 | } |
310 | |
311 | /* Helper function to try and extract shebang flags from the script body. |
312 | * If no shebang is found, return with success and COMPAT mode flag. |
313 | * The err arg is optional, can be used to get a detailed error string. |
314 | * The out_shebang_len arg is optional, can be used to trim the shebang from the script. |
315 | * Returns C_OK on success, and C_ERR on error. */ |
316 | int (sds body, uint64_t *out_flags, ssize_t *out_shebang_len, sds *err) { |
317 | ssize_t shebang_len = 0; |
318 | uint64_t script_flags = SCRIPT_FLAG_EVAL_COMPAT_MODE; |
319 | if (!strncmp(body, "#!" , 2)) { |
320 | int numparts,j; |
321 | char *shebang_end = strchr(body, '\n'); |
322 | if (shebang_end == NULL) { |
323 | if (err) |
324 | *err = sdsnew("Invalid script shebang" ); |
325 | return C_ERR; |
326 | } |
327 | shebang_len = shebang_end - body; |
328 | sds shebang = sdsnewlen(body, shebang_len); |
329 | sds *parts = sdssplitargs(shebang, &numparts); |
330 | sdsfree(shebang); |
331 | if (!parts || numparts == 0) { |
332 | if (err) |
333 | *err = sdsnew("Invalid engine in script shebang" ); |
334 | sdsfreesplitres(parts, numparts); |
335 | return C_ERR; |
336 | } |
337 | /* Verify lua interpreter was specified */ |
338 | if (strcmp(parts[0], "#!lua" )) { |
339 | if (err) |
340 | *err = sdscatfmt(sdsempty(), "Unexpected engine in script shebang: %s" , parts[0]); |
341 | sdsfreesplitres(parts, numparts); |
342 | return C_ERR; |
343 | } |
344 | script_flags &= ~SCRIPT_FLAG_EVAL_COMPAT_MODE; |
345 | for (j = 1; j < numparts; j++) { |
346 | if (!strncmp(parts[j], "flags=" , 6)) { |
347 | sdsrange(parts[j], 6, -1); |
348 | int numflags, jj; |
349 | sds *flags = sdssplitlen(parts[j], sdslen(parts[j]), "," , 1, &numflags); |
350 | for (jj = 0; jj < numflags; jj++) { |
351 | scriptFlag *sf; |
352 | for (sf = scripts_flags_def; sf->flag; sf++) { |
353 | if (!strcmp(flags[jj], sf->str)) break; |
354 | } |
355 | if (!sf->flag) { |
356 | if (err) |
357 | *err = sdscatfmt(sdsempty(), "Unexpected flag in script shebang: %s" , flags[jj]); |
358 | sdsfreesplitres(flags, numflags); |
359 | sdsfreesplitres(parts, numparts); |
360 | return C_ERR; |
361 | } |
362 | script_flags |= sf->flag; |
363 | } |
364 | sdsfreesplitres(flags, numflags); |
365 | } else { |
366 | /* We only support function flags options for lua scripts */ |
367 | if (err) |
368 | *err = sdscatfmt(sdsempty(), "Unknown lua shebang option: %s" , parts[j]); |
369 | sdsfreesplitres(parts, numparts); |
370 | return C_ERR; |
371 | } |
372 | } |
373 | sdsfreesplitres(parts, numparts); |
374 | } |
375 | if (out_shebang_len) |
376 | *out_shebang_len = shebang_len; |
377 | *out_flags = script_flags; |
378 | return C_OK; |
379 | } |
380 | |
381 | /* Try to extract command flags if we can, returns the modified flags. |
382 | * Note that it does not guarantee the command arguments are right. */ |
383 | uint64_t evalGetCommandFlags(client *c, uint64_t cmd_flags) { |
384 | char funcname[43]; |
385 | int evalsha = c->cmd->proc == evalShaCommand || c->cmd->proc == evalShaRoCommand; |
386 | if (evalsha && sdslen(c->argv[1]->ptr) != 40) |
387 | return cmd_flags; |
388 | evalCalcFunctionName(evalsha, c->argv[1]->ptr, funcname); |
389 | char *lua_cur_script = funcname + 2; |
390 | dictEntry *de = dictFind(lctx.lua_scripts, lua_cur_script); |
391 | uint64_t script_flags; |
392 | if (!de) { |
393 | if (evalsha) |
394 | return cmd_flags; |
395 | if (evalExtractShebangFlags(c->argv[1]->ptr, &script_flags, NULL, NULL) == C_ERR) |
396 | return cmd_flags; |
397 | } else { |
398 | luaScript *l = dictGetVal(de); |
399 | script_flags = l->flags; |
400 | } |
401 | if (script_flags & SCRIPT_FLAG_EVAL_COMPAT_MODE) |
402 | return cmd_flags; |
403 | return scriptFlagsToCmdFlags(cmd_flags, script_flags); |
404 | } |
405 | |
406 | /* Define a Lua function with the specified body. |
407 | * The function name will be generated in the following form: |
408 | * |
409 | * f_<hex sha1 sum> |
410 | * |
411 | * The function increments the reference count of the 'body' object as a |
412 | * side effect of a successful call. |
413 | * |
414 | * On success a pointer to an SDS string representing the function SHA1 of the |
415 | * just added function is returned (and will be valid until the next call |
416 | * to scriptingReset() function), otherwise NULL is returned. |
417 | * |
418 | * The function handles the fact of being called with a script that already |
419 | * exists, and in such a case, it behaves like in the success case. |
420 | * |
421 | * If 'c' is not NULL, on error the client is informed with an appropriate |
422 | * error describing the nature of the problem and the Lua interpreter error. */ |
423 | sds luaCreateFunction(client *c, robj *body) { |
424 | char funcname[43]; |
425 | dictEntry *de; |
426 | uint64_t script_flags; |
427 | |
428 | funcname[0] = 'f'; |
429 | funcname[1] = '_'; |
430 | sha1hex(funcname+2,body->ptr,sdslen(body->ptr)); |
431 | |
432 | if ((de = dictFind(lctx.lua_scripts,funcname+2)) != NULL) { |
433 | return dictGetKey(de); |
434 | } |
435 | |
436 | /* Handle shebang header in script code */ |
437 | ssize_t shebang_len = 0; |
438 | sds err = NULL; |
439 | if (evalExtractShebangFlags(body->ptr, &script_flags, &shebang_len, &err) == C_ERR) { |
440 | addReplyErrorSds(c, err); |
441 | return NULL; |
442 | } |
443 | |
444 | /* Note that in case of a shebang line we skip it but keep the line feed to conserve the user's line numbers */ |
445 | if (luaL_loadbuffer(lctx.lua,(char*)body->ptr + shebang_len,sdslen(body->ptr) - shebang_len,"@user_script" )) { |
446 | if (c != NULL) { |
447 | addReplyErrorFormat(c, |
448 | "Error compiling script (new function): %s" , |
449 | lua_tostring(lctx.lua,-1)); |
450 | } |
451 | lua_pop(lctx.lua,1); |
452 | return NULL; |
453 | } |
454 | |
455 | serverAssert(lua_isfunction(lctx.lua, -1)); |
456 | |
457 | lua_setfield(lctx.lua, LUA_REGISTRYINDEX, funcname); |
458 | |
459 | /* We also save a SHA1 -> Original script map in a dictionary |
460 | * so that we can replicate / write in the AOF all the |
461 | * EVALSHA commands as EVAL using the original script. */ |
462 | luaScript *l = zcalloc(sizeof(luaScript)); |
463 | l->body = body; |
464 | l->flags = script_flags; |
465 | sds sha = sdsnewlen(funcname+2,40); |
466 | int retval = dictAdd(lctx.lua_scripts,sha,l); |
467 | serverAssertWithInfo(c ? c : lctx.lua_client,NULL,retval == DICT_OK); |
468 | lctx.lua_scripts_mem += sdsZmallocSize(sha) + getStringObjectSdsUsedMemory(body); |
469 | incrRefCount(body); |
470 | return sha; |
471 | } |
472 | |
473 | void prepareLuaClient(void) { |
474 | /* Select the right DB in the context of the Lua client */ |
475 | selectDb(lctx.lua_client,server.script_caller->db->id); |
476 | lctx.lua_client->resp = 2; /* Default is RESP2, scripts can change it. */ |
477 | |
478 | /* If we are in MULTI context, flag Lua client as CLIENT_MULTI. */ |
479 | if (server.script_caller->flags & CLIENT_MULTI) { |
480 | lctx.lua_client->flags |= CLIENT_MULTI; |
481 | } |
482 | } |
483 | |
484 | void resetLuaClient(void) { |
485 | /* After the script done, remove the MULTI state. */ |
486 | lctx.lua_client->flags &= ~CLIENT_MULTI; |
487 | } |
488 | |
489 | void evalGenericCommand(client *c, int evalsha) { |
490 | lua_State *lua = lctx.lua; |
491 | char funcname[43]; |
492 | long long numkeys; |
493 | |
494 | /* Get the number of arguments that are keys */ |
495 | if (getLongLongFromObjectOrReply(c,c->argv[2],&numkeys,NULL) != C_OK) |
496 | return; |
497 | if (numkeys > (c->argc - 3)) { |
498 | addReplyError(c,"Number of keys can't be greater than number of args" ); |
499 | return; |
500 | } else if (numkeys < 0) { |
501 | addReplyError(c,"Number of keys can't be negative" ); |
502 | return; |
503 | } |
504 | |
505 | evalCalcFunctionName(evalsha, c->argv[1]->ptr, funcname); |
506 | |
507 | /* Push the pcall error handler function on the stack. */ |
508 | lua_getglobal(lua, "__redis__err__handler" ); |
509 | |
510 | /* Try to lookup the Lua function */ |
511 | lua_getfield(lua, LUA_REGISTRYINDEX, funcname); |
512 | if (lua_isnil(lua,-1)) { |
513 | lua_pop(lua,1); /* remove the nil from the stack */ |
514 | /* Function not defined... let's define it if we have the |
515 | * body of the function. If this is an EVALSHA call we can just |
516 | * return an error. */ |
517 | if (evalsha) { |
518 | lua_pop(lua,1); /* remove the error handler from the stack. */ |
519 | addReplyErrorObject(c, shared.noscripterr); |
520 | return; |
521 | } |
522 | if (luaCreateFunction(c,c->argv[1]) == NULL) { |
523 | lua_pop(lua,1); /* remove the error handler from the stack. */ |
524 | /* The error is sent to the client by luaCreateFunction() |
525 | * itself when it returns NULL. */ |
526 | return; |
527 | } |
528 | /* Now the following is guaranteed to return non nil */ |
529 | lua_getfield(lua, LUA_REGISTRYINDEX, funcname); |
530 | serverAssert(!lua_isnil(lua,-1)); |
531 | } |
532 | |
533 | char *lua_cur_script = funcname + 2; |
534 | dictEntry *de = dictFind(lctx.lua_scripts, lua_cur_script); |
535 | luaScript *l = dictGetVal(de); |
536 | int ro = c->cmd->proc == evalRoCommand || c->cmd->proc == evalShaRoCommand; |
537 | |
538 | scriptRunCtx rctx; |
539 | if (scriptPrepareForRun(&rctx, lctx.lua_client, c, lua_cur_script, l->flags, ro) != C_OK) { |
540 | lua_pop(lua,2); /* Remove the function and error handler. */ |
541 | return; |
542 | } |
543 | rctx.flags |= SCRIPT_EVAL_MODE; /* mark the current run as EVAL (as opposed to FCALL) so we'll |
544 | get appropriate error messages and logs */ |
545 | |
546 | luaCallFunction(&rctx, lua, c->argv+3, numkeys, c->argv+3+numkeys, c->argc-3-numkeys, ldb.active); |
547 | lua_pop(lua,1); /* Remove the error handler. */ |
548 | scriptResetRun(&rctx); |
549 | } |
550 | |
551 | void evalCommand(client *c) { |
552 | /* Explicitly feed monitor here so that lua commands appear after their |
553 | * script command. */ |
554 | replicationFeedMonitors(c,server.monitors,c->db->id,c->argv,c->argc); |
555 | if (!(c->flags & CLIENT_LUA_DEBUG)) |
556 | evalGenericCommand(c,0); |
557 | else |
558 | evalGenericCommandWithDebugging(c,0); |
559 | } |
560 | |
561 | void evalRoCommand(client *c) { |
562 | evalCommand(c); |
563 | } |
564 | |
565 | void evalShaCommand(client *c) { |
566 | /* Explicitly feed monitor here so that lua commands appear after their |
567 | * script command. */ |
568 | replicationFeedMonitors(c,server.monitors,c->db->id,c->argv,c->argc); |
569 | if (sdslen(c->argv[1]->ptr) != 40) { |
570 | /* We know that a match is not possible if the provided SHA is |
571 | * not the right length. So we return an error ASAP, this way |
572 | * evalGenericCommand() can be implemented without string length |
573 | * sanity check */ |
574 | addReplyErrorObject(c, shared.noscripterr); |
575 | return; |
576 | } |
577 | if (!(c->flags & CLIENT_LUA_DEBUG)) |
578 | evalGenericCommand(c,1); |
579 | else { |
580 | addReplyError(c,"Please use EVAL instead of EVALSHA for debugging" ); |
581 | return; |
582 | } |
583 | } |
584 | |
585 | void evalShaRoCommand(client *c) { |
586 | evalShaCommand(c); |
587 | } |
588 | |
589 | void scriptCommand(client *c) { |
590 | if (c->argc == 2 && !strcasecmp(c->argv[1]->ptr,"help" )) { |
591 | const char *help[] = { |
592 | "DEBUG (YES|SYNC|NO)" , |
593 | " Set the debug mode for subsequent scripts executed." , |
594 | "EXISTS <sha1> [<sha1> ...]" , |
595 | " Return information about the existence of the scripts in the script cache." , |
596 | "FLUSH [ASYNC|SYNC]" , |
597 | " Flush the Lua scripts cache. Very dangerous on replicas." , |
598 | " When called without the optional mode argument, the behavior is determined by the" , |
599 | " lazyfree-lazy-user-flush configuration directive. Valid modes are:" , |
600 | " * ASYNC: Asynchronously flush the scripts cache." , |
601 | " * SYNC: Synchronously flush the scripts cache." , |
602 | "KILL" , |
603 | " Kill the currently executing Lua script." , |
604 | "LOAD <script>" , |
605 | " Load a script into the scripts cache without executing it." , |
606 | NULL |
607 | }; |
608 | addReplyHelp(c, help); |
609 | } else if (c->argc >= 2 && !strcasecmp(c->argv[1]->ptr,"flush" )) { |
610 | int async = 0; |
611 | if (c->argc == 3 && !strcasecmp(c->argv[2]->ptr,"sync" )) { |
612 | async = 0; |
613 | } else if (c->argc == 3 && !strcasecmp(c->argv[2]->ptr,"async" )) { |
614 | async = 1; |
615 | } else if (c->argc == 2) { |
616 | async = server.lazyfree_lazy_user_flush ? 1 : 0; |
617 | } else { |
618 | addReplyError(c,"SCRIPT FLUSH only support SYNC|ASYNC option" ); |
619 | return; |
620 | } |
621 | scriptingReset(async); |
622 | addReply(c,shared.ok); |
623 | } else if (c->argc >= 2 && !strcasecmp(c->argv[1]->ptr,"exists" )) { |
624 | int j; |
625 | |
626 | addReplyArrayLen(c, c->argc-2); |
627 | for (j = 2; j < c->argc; j++) { |
628 | if (dictFind(lctx.lua_scripts,c->argv[j]->ptr)) |
629 | addReply(c,shared.cone); |
630 | else |
631 | addReply(c,shared.czero); |
632 | } |
633 | } else if (c->argc == 3 && !strcasecmp(c->argv[1]->ptr,"load" )) { |
634 | sds sha = luaCreateFunction(c,c->argv[2]); |
635 | if (sha == NULL) return; /* The error was sent by luaCreateFunction(). */ |
636 | addReplyBulkCBuffer(c,sha,40); |
637 | } else if (c->argc == 2 && !strcasecmp(c->argv[1]->ptr,"kill" )) { |
638 | scriptKill(c, 1); |
639 | } else if (c->argc == 3 && !strcasecmp(c->argv[1]->ptr,"debug" )) { |
640 | if (clientHasPendingReplies(c)) { |
641 | addReplyError(c,"SCRIPT DEBUG must be called outside a pipeline" ); |
642 | return; |
643 | } |
644 | if (!strcasecmp(c->argv[2]->ptr,"no" )) { |
645 | ldbDisable(c); |
646 | addReply(c,shared.ok); |
647 | } else if (!strcasecmp(c->argv[2]->ptr,"yes" )) { |
648 | ldbEnable(c); |
649 | addReply(c,shared.ok); |
650 | } else if (!strcasecmp(c->argv[2]->ptr,"sync" )) { |
651 | ldbEnable(c); |
652 | addReply(c,shared.ok); |
653 | c->flags |= CLIENT_LUA_DEBUG_SYNC; |
654 | } else { |
655 | addReplyError(c,"Use SCRIPT DEBUG YES/SYNC/NO" ); |
656 | return; |
657 | } |
658 | } else { |
659 | addReplySubcommandSyntaxError(c); |
660 | } |
661 | } |
662 | |
663 | unsigned long evalMemory() { |
664 | return luaMemory(lctx.lua); |
665 | } |
666 | |
667 | dict* evalScriptsDict() { |
668 | return lctx.lua_scripts; |
669 | } |
670 | |
671 | unsigned long evalScriptsMemory() { |
672 | return lctx.lua_scripts_mem + |
673 | dictSize(lctx.lua_scripts) * (sizeof(dictEntry) + sizeof(luaScript)) + |
674 | dictSlots(lctx.lua_scripts) * sizeof(dictEntry*); |
675 | } |
676 | |
677 | /* --------------------------------------------------------------------------- |
678 | * LDB: Redis Lua debugging facilities |
679 | * ------------------------------------------------------------------------- */ |
680 | |
681 | /* Initialize Lua debugger data structures. */ |
682 | void ldbInit(void) { |
683 | ldb.conn = NULL; |
684 | ldb.active = 0; |
685 | ldb.logs = listCreate(); |
686 | listSetFreeMethod(ldb.logs,(void (*)(void*))sdsfree); |
687 | ldb.children = listCreate(); |
688 | ldb.src = NULL; |
689 | ldb.lines = 0; |
690 | ldb.cbuf = sdsempty(); |
691 | } |
692 | |
693 | /* Remove all the pending messages in the specified list. */ |
694 | void ldbFlushLog(list *log) { |
695 | listNode *ln; |
696 | |
697 | while((ln = listFirst(log)) != NULL) |
698 | listDelNode(log,ln); |
699 | } |
700 | |
701 | int ldbIsEnabled(){ |
702 | return ldb.active && ldb.step; |
703 | } |
704 | |
705 | /* Enable debug mode of Lua scripts for this client. */ |
706 | void ldbEnable(client *c) { |
707 | c->flags |= CLIENT_LUA_DEBUG; |
708 | ldbFlushLog(ldb.logs); |
709 | ldb.conn = c->conn; |
710 | ldb.step = 1; |
711 | ldb.bpcount = 0; |
712 | ldb.luabp = 0; |
713 | sdsfree(ldb.cbuf); |
714 | ldb.cbuf = sdsempty(); |
715 | ldb.maxlen = LDB_MAX_LEN_DEFAULT; |
716 | ldb.maxlen_hint_sent = 0; |
717 | } |
718 | |
719 | /* Exit debugging mode from the POV of client. This function is not enough |
720 | * to properly shut down a client debugging session, see ldbEndSession() |
721 | * for more information. */ |
722 | void ldbDisable(client *c) { |
723 | c->flags &= ~(CLIENT_LUA_DEBUG|CLIENT_LUA_DEBUG_SYNC); |
724 | } |
725 | |
726 | /* Append a log entry to the specified LDB log. */ |
727 | void ldbLog(sds entry) { |
728 | listAddNodeTail(ldb.logs,entry); |
729 | } |
730 | |
731 | /* A version of ldbLog() which prevents producing logs greater than |
732 | * ldb.maxlen. The first time the limit is reached a hint is generated |
733 | * to inform the user that reply trimming can be disabled using the |
734 | * debugger "maxlen" command. */ |
735 | void ldbLogWithMaxLen(sds entry) { |
736 | int trimmed = 0; |
737 | if (ldb.maxlen && sdslen(entry) > ldb.maxlen) { |
738 | sdsrange(entry,0,ldb.maxlen-1); |
739 | entry = sdscatlen(entry," ..." ,4); |
740 | trimmed = 1; |
741 | } |
742 | ldbLog(entry); |
743 | if (trimmed && ldb.maxlen_hint_sent == 0) { |
744 | ldb.maxlen_hint_sent = 1; |
745 | ldbLog(sdsnew( |
746 | "<hint> The above reply was trimmed. Use 'maxlen 0' to disable trimming." )); |
747 | } |
748 | } |
749 | |
750 | /* Send ldb.logs to the debugging client as a multi-bulk reply |
751 | * consisting of simple strings. Log entries which include newlines have them |
752 | * replaced with spaces. The entries sent are also consumed. */ |
753 | void ldbSendLogs(void) { |
754 | sds proto = sdsempty(); |
755 | proto = sdscatfmt(proto,"*%i\r\n" , (int)listLength(ldb.logs)); |
756 | while(listLength(ldb.logs)) { |
757 | listNode *ln = listFirst(ldb.logs); |
758 | proto = sdscatlen(proto,"+" ,1); |
759 | sdsmapchars(ln->value,"\r\n" ," " ,2); |
760 | proto = sdscatsds(proto,ln->value); |
761 | proto = sdscatlen(proto,"\r\n" ,2); |
762 | listDelNode(ldb.logs,ln); |
763 | } |
764 | if (connWrite(ldb.conn,proto,sdslen(proto)) == -1) { |
765 | /* Avoid warning. We don't check the return value of write() |
766 | * since the next read() will catch the I/O error and will |
767 | * close the debugging session. */ |
768 | } |
769 | sdsfree(proto); |
770 | } |
771 | |
772 | /* Start a debugging session before calling EVAL implementation. |
773 | * The technique we use is to capture the client socket file descriptor, |
774 | * in order to perform direct I/O with it from within Lua hooks. This |
775 | * way we don't have to re-enter Redis in order to handle I/O. |
776 | * |
777 | * The function returns 1 if the caller should proceed to call EVAL, |
778 | * and 0 if instead the caller should abort the operation (this happens |
779 | * for the parent in a forked session, since it's up to the children |
780 | * to continue, or when fork returned an error). |
781 | * |
782 | * The caller should call ldbEndSession() only if ldbStartSession() |
783 | * returned 1. */ |
784 | int ldbStartSession(client *c) { |
785 | ldb.forked = (c->flags & CLIENT_LUA_DEBUG_SYNC) == 0; |
786 | if (ldb.forked) { |
787 | pid_t cp = redisFork(CHILD_TYPE_LDB); |
788 | if (cp == -1) { |
789 | addReplyErrorFormat(c,"Fork() failed: can't run EVAL in debugging mode: %s" , strerror(errno)); |
790 | return 0; |
791 | } else if (cp == 0) { |
792 | /* Child. Let's ignore important signals handled by the parent. */ |
793 | struct sigaction act; |
794 | sigemptyset(&act.sa_mask); |
795 | act.sa_flags = 0; |
796 | act.sa_handler = SIG_IGN; |
797 | sigaction(SIGTERM, &act, NULL); |
798 | sigaction(SIGINT, &act, NULL); |
799 | |
800 | /* Log the creation of the child and close the listening |
801 | * socket to make sure if the parent crashes a reset is sent |
802 | * to the clients. */ |
803 | serverLog(LL_WARNING,"Redis forked for debugging eval" ); |
804 | } else { |
805 | /* Parent */ |
806 | listAddNodeTail(ldb.children,(void*)(unsigned long)cp); |
807 | freeClientAsync(c); /* Close the client in the parent side. */ |
808 | return 0; |
809 | } |
810 | } else { |
811 | serverLog(LL_WARNING, |
812 | "Redis synchronous debugging eval session started" ); |
813 | } |
814 | |
815 | /* Setup our debugging session. */ |
816 | connBlock(ldb.conn); |
817 | connSendTimeout(ldb.conn,5000); |
818 | ldb.active = 1; |
819 | |
820 | /* First argument of EVAL is the script itself. We split it into different |
821 | * lines since this is the way the debugger accesses the source code. */ |
822 | sds srcstring = sdsdup(c->argv[1]->ptr); |
823 | size_t srclen = sdslen(srcstring); |
824 | while(srclen && (srcstring[srclen-1] == '\n' || |
825 | srcstring[srclen-1] == '\r')) |
826 | { |
827 | srcstring[--srclen] = '\0'; |
828 | } |
829 | sdssetlen(srcstring,srclen); |
830 | ldb.src = sdssplitlen(srcstring,sdslen(srcstring),"\n" ,1,&ldb.lines); |
831 | sdsfree(srcstring); |
832 | return 1; |
833 | } |
834 | |
835 | /* End a debugging session after the EVAL call with debugging enabled |
836 | * returned. */ |
837 | void ldbEndSession(client *c) { |
838 | /* Emit the remaining logs and an <endsession> mark. */ |
839 | ldbLog(sdsnew("<endsession>" )); |
840 | ldbSendLogs(); |
841 | |
842 | /* If it's a fork()ed session, we just exit. */ |
843 | if (ldb.forked) { |
844 | writeToClient(c,0); |
845 | serverLog(LL_WARNING,"Lua debugging session child exiting" ); |
846 | exitFromChild(0); |
847 | } else { |
848 | serverLog(LL_WARNING, |
849 | "Redis synchronous debugging eval session ended" ); |
850 | } |
851 | |
852 | /* Otherwise let's restore client's state. */ |
853 | connNonBlock(ldb.conn); |
854 | connSendTimeout(ldb.conn,0); |
855 | |
856 | /* Close the client connection after sending the final EVAL reply |
857 | * in order to signal the end of the debugging session. */ |
858 | c->flags |= CLIENT_CLOSE_AFTER_REPLY; |
859 | |
860 | /* Cleanup. */ |
861 | sdsfreesplitres(ldb.src,ldb.lines); |
862 | ldb.lines = 0; |
863 | ldb.active = 0; |
864 | } |
865 | |
866 | /* If the specified pid is among the list of children spawned for |
867 | * forked debugging sessions, it is removed from the children list. |
868 | * If the pid was found non-zero is returned. */ |
869 | int ldbRemoveChild(pid_t pid) { |
870 | listNode *ln = listSearchKey(ldb.children,(void*)(unsigned long)pid); |
871 | if (ln) { |
872 | listDelNode(ldb.children,ln); |
873 | return 1; |
874 | } |
875 | return 0; |
876 | } |
877 | |
878 | /* Return the number of children we still did not receive termination |
879 | * acknowledge via wait() in the parent process. */ |
880 | int ldbPendingChildren(void) { |
881 | return listLength(ldb.children); |
882 | } |
883 | |
884 | /* Kill all the forked sessions. */ |
885 | void ldbKillForkedSessions(void) { |
886 | listIter li; |
887 | listNode *ln; |
888 | |
889 | listRewind(ldb.children,&li); |
890 | while((ln = listNext(&li))) { |
891 | pid_t pid = (unsigned long) ln->value; |
892 | serverLog(LL_WARNING,"Killing debugging session %ld" ,(long)pid); |
893 | kill(pid,SIGKILL); |
894 | } |
895 | listRelease(ldb.children); |
896 | ldb.children = listCreate(); |
897 | } |
898 | |
899 | /* Wrapper for EVAL / EVALSHA that enables debugging, and makes sure |
900 | * that when EVAL returns, whatever happened, the session is ended. */ |
901 | void evalGenericCommandWithDebugging(client *c, int evalsha) { |
902 | if (ldbStartSession(c)) { |
903 | evalGenericCommand(c,evalsha); |
904 | ldbEndSession(c); |
905 | } else { |
906 | ldbDisable(c); |
907 | } |
908 | } |
909 | |
910 | /* Return a pointer to ldb.src source code line, considering line to be |
911 | * one-based, and returning a special string for out of range lines. */ |
912 | char *ldbGetSourceLine(int line) { |
913 | int idx = line-1; |
914 | if (idx < 0 || idx >= ldb.lines) return "<out of range source code line>" ; |
915 | return ldb.src[idx]; |
916 | } |
917 | |
918 | /* Return true if there is a breakpoint in the specified line. */ |
919 | int ldbIsBreakpoint(int line) { |
920 | int j; |
921 | |
922 | for (j = 0; j < ldb.bpcount; j++) |
923 | if (ldb.bp[j] == line) return 1; |
924 | return 0; |
925 | } |
926 | |
927 | /* Add the specified breakpoint. Ignore it if we already reached the max. |
928 | * Returns 1 if the breakpoint was added (or was already set). 0 if there is |
929 | * no space for the breakpoint or if the line is invalid. */ |
930 | int ldbAddBreakpoint(int line) { |
931 | if (line <= 0 || line > ldb.lines) return 0; |
932 | if (!ldbIsBreakpoint(line) && ldb.bpcount != LDB_BREAKPOINTS_MAX) { |
933 | ldb.bp[ldb.bpcount++] = line; |
934 | return 1; |
935 | } |
936 | return 0; |
937 | } |
938 | |
939 | /* Remove the specified breakpoint, returning 1 if the operation was |
940 | * performed or 0 if there was no such breakpoint. */ |
941 | int ldbDelBreakpoint(int line) { |
942 | int j; |
943 | |
944 | for (j = 0; j < ldb.bpcount; j++) { |
945 | if (ldb.bp[j] == line) { |
946 | ldb.bpcount--; |
947 | memmove(ldb.bp+j,ldb.bp+j+1,ldb.bpcount-j); |
948 | return 1; |
949 | } |
950 | } |
951 | return 0; |
952 | } |
953 | |
954 | /* Expect a valid multi-bulk command in the debugging client query buffer. |
955 | * On success the command is parsed and returned as an array of SDS strings, |
956 | * otherwise NULL is returned and there is to read more buffer. */ |
957 | sds *ldbReplParseCommand(int *argcp, char** err) { |
958 | static char* protocol_error = "protocol error" ; |
959 | sds *argv = NULL; |
960 | int argc = 0; |
961 | if (sdslen(ldb.cbuf) == 0) return NULL; |
962 | |
963 | /* Working on a copy is simpler in this case. We can modify it freely |
964 | * for the sake of simpler parsing. */ |
965 | sds copy = sdsdup(ldb.cbuf); |
966 | char *p = copy; |
967 | |
968 | /* This Redis protocol parser is a joke... just the simplest thing that |
969 | * works in this context. It is also very forgiving regarding broken |
970 | * protocol. */ |
971 | |
972 | /* Seek and parse *<count>\r\n. */ |
973 | p = strchr(p,'*'); if (!p) goto protoerr; |
974 | char *plen = p+1; /* Multi bulk len pointer. */ |
975 | p = strstr(p,"\r\n" ); if (!p) goto keep_reading; |
976 | *p = '\0'; p += 2; |
977 | *argcp = atoi(plen); |
978 | if (*argcp <= 0 || *argcp > 1024) goto protoerr; |
979 | |
980 | /* Parse each argument. */ |
981 | argv = zmalloc(sizeof(sds)*(*argcp)); |
982 | argc = 0; |
983 | while(argc < *argcp) { |
984 | /* reached the end but there should be more data to read */ |
985 | if (*p == '\0') goto keep_reading; |
986 | |
987 | if (*p != '$') goto protoerr; |
988 | plen = p+1; /* Bulk string len pointer. */ |
989 | p = strstr(p,"\r\n" ); if (!p) goto keep_reading; |
990 | *p = '\0'; p += 2; |
991 | int slen = atoi(plen); /* Length of this arg. */ |
992 | if (slen <= 0 || slen > 1024) goto protoerr; |
993 | if ((size_t)(p + slen + 2 - copy) > sdslen(copy) ) goto keep_reading; |
994 | argv[argc++] = sdsnewlen(p,slen); |
995 | p += slen; /* Skip the already parsed argument. */ |
996 | if (p[0] != '\r' || p[1] != '\n') goto protoerr; |
997 | p += 2; /* Skip \r\n. */ |
998 | } |
999 | sdsfree(copy); |
1000 | return argv; |
1001 | |
1002 | protoerr: |
1003 | *err = protocol_error; |
1004 | keep_reading: |
1005 | sdsfreesplitres(argv,argc); |
1006 | sdsfree(copy); |
1007 | return NULL; |
1008 | } |
1009 | |
1010 | /* Log the specified line in the Lua debugger output. */ |
1011 | void ldbLogSourceLine(int lnum) { |
1012 | char *line = ldbGetSourceLine(lnum); |
1013 | char *prefix; |
1014 | int bp = ldbIsBreakpoint(lnum); |
1015 | int current = ldb.currentline == lnum; |
1016 | |
1017 | if (current && bp) |
1018 | prefix = "->#" ; |
1019 | else if (current) |
1020 | prefix = "-> " ; |
1021 | else if (bp) |
1022 | prefix = " #" ; |
1023 | else |
1024 | prefix = " " ; |
1025 | sds thisline = sdscatprintf(sdsempty(),"%s%-3d %s" , prefix, lnum, line); |
1026 | ldbLog(thisline); |
1027 | } |
1028 | |
1029 | /* Implement the "list" command of the Lua debugger. If around is 0 |
1030 | * the whole file is listed, otherwise only a small portion of the file |
1031 | * around the specified line is shown. When a line number is specified |
1032 | * the amount of context (lines before/after) is specified via the |
1033 | * 'context' argument. */ |
1034 | void ldbList(int around, int context) { |
1035 | int j; |
1036 | |
1037 | for (j = 1; j <= ldb.lines; j++) { |
1038 | if (around != 0 && abs(around-j) > context) continue; |
1039 | ldbLogSourceLine(j); |
1040 | } |
1041 | } |
1042 | |
1043 | /* Append a human readable representation of the Lua value at position 'idx' |
1044 | * on the stack of the 'lua' state, to the SDS string passed as argument. |
1045 | * The new SDS string with the represented value attached is returned. |
1046 | * Used in order to implement ldbLogStackValue(). |
1047 | * |
1048 | * The element is not automatically removed from the stack, nor it is |
1049 | * converted to a different type. */ |
1050 | #define LDB_MAX_VALUES_DEPTH (LUA_MINSTACK/2) |
1051 | sds ldbCatStackValueRec(sds s, lua_State *lua, int idx, int level) { |
1052 | int t = lua_type(lua,idx); |
1053 | |
1054 | if (level++ == LDB_MAX_VALUES_DEPTH) |
1055 | return sdscat(s,"<max recursion level reached! Nested table?>" ); |
1056 | |
1057 | switch(t) { |
1058 | case LUA_TSTRING: |
1059 | { |
1060 | size_t strl; |
1061 | char *strp = (char*)lua_tolstring(lua,idx,&strl); |
1062 | s = sdscatrepr(s,strp,strl); |
1063 | } |
1064 | break; |
1065 | case LUA_TBOOLEAN: |
1066 | s = sdscat(s,lua_toboolean(lua,idx) ? "true" : "false" ); |
1067 | break; |
1068 | case LUA_TNUMBER: |
1069 | s = sdscatprintf(s,"%g" ,(double)lua_tonumber(lua,idx)); |
1070 | break; |
1071 | case LUA_TNIL: |
1072 | s = sdscatlen(s,"nil" ,3); |
1073 | break; |
1074 | case LUA_TTABLE: |
1075 | { |
1076 | int expected_index = 1; /* First index we expect in an array. */ |
1077 | int is_array = 1; /* Will be set to null if check fails. */ |
1078 | /* Note: we create two representations at the same time, one |
1079 | * assuming the table is an array, one assuming it is not. At the |
1080 | * end we know what is true and select the right one. */ |
1081 | sds repr1 = sdsempty(); |
1082 | sds repr2 = sdsempty(); |
1083 | lua_pushnil(lua); /* The first key to start the iteration is nil. */ |
1084 | while (lua_next(lua,idx-1)) { |
1085 | /* Test if so far the table looks like an array. */ |
1086 | if (is_array && |
1087 | (lua_type(lua,-2) != LUA_TNUMBER || |
1088 | lua_tonumber(lua,-2) != expected_index)) is_array = 0; |
1089 | /* Stack now: table, key, value */ |
1090 | /* Array repr. */ |
1091 | repr1 = ldbCatStackValueRec(repr1,lua,-1,level); |
1092 | repr1 = sdscatlen(repr1,"; " ,2); |
1093 | /* Full repr. */ |
1094 | repr2 = sdscatlen(repr2,"[" ,1); |
1095 | repr2 = ldbCatStackValueRec(repr2,lua,-2,level); |
1096 | repr2 = sdscatlen(repr2,"]=" ,2); |
1097 | repr2 = ldbCatStackValueRec(repr2,lua,-1,level); |
1098 | repr2 = sdscatlen(repr2,"; " ,2); |
1099 | lua_pop(lua,1); /* Stack: table, key. Ready for next iteration. */ |
1100 | expected_index++; |
1101 | } |
1102 | /* Strip the last " ;" from both the representations. */ |
1103 | if (sdslen(repr1)) sdsrange(repr1,0,-3); |
1104 | if (sdslen(repr2)) sdsrange(repr2,0,-3); |
1105 | /* Select the right one and discard the other. */ |
1106 | s = sdscatlen(s,"{" ,1); |
1107 | s = sdscatsds(s,is_array ? repr1 : repr2); |
1108 | s = sdscatlen(s,"}" ,1); |
1109 | sdsfree(repr1); |
1110 | sdsfree(repr2); |
1111 | } |
1112 | break; |
1113 | case LUA_TFUNCTION: |
1114 | case LUA_TUSERDATA: |
1115 | case LUA_TTHREAD: |
1116 | case LUA_TLIGHTUSERDATA: |
1117 | { |
1118 | const void *p = lua_topointer(lua,idx); |
1119 | char *typename = "unknown" ; |
1120 | if (t == LUA_TFUNCTION) typename = "function" ; |
1121 | else if (t == LUA_TUSERDATA) typename = "userdata" ; |
1122 | else if (t == LUA_TTHREAD) typename = "thread" ; |
1123 | else if (t == LUA_TLIGHTUSERDATA) typename = "light-userdata" ; |
1124 | s = sdscatprintf(s,"\"%s@%p\"" ,typename,p); |
1125 | } |
1126 | break; |
1127 | default: |
1128 | s = sdscat(s,"\"<unknown-lua-type>\"" ); |
1129 | break; |
1130 | } |
1131 | return s; |
1132 | } |
1133 | |
1134 | /* Higher level wrapper for ldbCatStackValueRec() that just uses an initial |
1135 | * recursion level of '0'. */ |
1136 | sds ldbCatStackValue(sds s, lua_State *lua, int idx) { |
1137 | return ldbCatStackValueRec(s,lua,idx,0); |
1138 | } |
1139 | |
1140 | /* Produce a debugger log entry representing the value of the Lua object |
1141 | * currently on the top of the stack. The element is not popped nor modified. |
1142 | * Check ldbCatStackValue() for the actual implementation. */ |
1143 | void ldbLogStackValue(lua_State *lua, char *prefix) { |
1144 | sds s = sdsnew(prefix); |
1145 | s = ldbCatStackValue(s,lua,-1); |
1146 | ldbLogWithMaxLen(s); |
1147 | } |
1148 | |
1149 | char *ldbRedisProtocolToHuman_Int(sds *o, char *reply); |
1150 | char *ldbRedisProtocolToHuman_Bulk(sds *o, char *reply); |
1151 | char *ldbRedisProtocolToHuman_Status(sds *o, char *reply); |
1152 | char *ldbRedisProtocolToHuman_MultiBulk(sds *o, char *reply); |
1153 | char *ldbRedisProtocolToHuman_Set(sds *o, char *reply); |
1154 | char *ldbRedisProtocolToHuman_Map(sds *o, char *reply); |
1155 | char *ldbRedisProtocolToHuman_Null(sds *o, char *reply); |
1156 | char *ldbRedisProtocolToHuman_Bool(sds *o, char *reply); |
1157 | char *ldbRedisProtocolToHuman_Double(sds *o, char *reply); |
1158 | |
1159 | /* Get Redis protocol from 'reply' and appends it in human readable form to |
1160 | * the passed SDS string 'o'. |
1161 | * |
1162 | * Note that the SDS string is passed by reference (pointer of pointer to |
1163 | * char*) so that we can return a modified pointer, as for SDS semantics. */ |
1164 | char *ldbRedisProtocolToHuman(sds *o, char *reply) { |
1165 | char *p = reply; |
1166 | switch(*p) { |
1167 | case ':': p = ldbRedisProtocolToHuman_Int(o,reply); break; |
1168 | case '$': p = ldbRedisProtocolToHuman_Bulk(o,reply); break; |
1169 | case '+': p = ldbRedisProtocolToHuman_Status(o,reply); break; |
1170 | case '-': p = ldbRedisProtocolToHuman_Status(o,reply); break; |
1171 | case '*': p = ldbRedisProtocolToHuman_MultiBulk(o,reply); break; |
1172 | case '~': p = ldbRedisProtocolToHuman_Set(o,reply); break; |
1173 | case '%': p = ldbRedisProtocolToHuman_Map(o,reply); break; |
1174 | case '_': p = ldbRedisProtocolToHuman_Null(o,reply); break; |
1175 | case '#': p = ldbRedisProtocolToHuman_Bool(o,reply); break; |
1176 | case ',': p = ldbRedisProtocolToHuman_Double(o,reply); break; |
1177 | } |
1178 | return p; |
1179 | } |
1180 | |
1181 | /* The following functions are helpers for ldbRedisProtocolToHuman(), each |
1182 | * take care of a given Redis return type. */ |
1183 | |
1184 | char *ldbRedisProtocolToHuman_Int(sds *o, char *reply) { |
1185 | char *p = strchr(reply+1,'\r'); |
1186 | *o = sdscatlen(*o,reply+1,p-reply-1); |
1187 | return p+2; |
1188 | } |
1189 | |
1190 | char *ldbRedisProtocolToHuman_Bulk(sds *o, char *reply) { |
1191 | char *p = strchr(reply+1,'\r'); |
1192 | long long bulklen; |
1193 | |
1194 | string2ll(reply+1,p-reply-1,&bulklen); |
1195 | if (bulklen == -1) { |
1196 | *o = sdscatlen(*o,"NULL" ,4); |
1197 | return p+2; |
1198 | } else { |
1199 | *o = sdscatrepr(*o,p+2,bulklen); |
1200 | return p+2+bulklen+2; |
1201 | } |
1202 | } |
1203 | |
1204 | char *ldbRedisProtocolToHuman_Status(sds *o, char *reply) { |
1205 | char *p = strchr(reply+1,'\r'); |
1206 | |
1207 | *o = sdscatrepr(*o,reply,p-reply); |
1208 | return p+2; |
1209 | } |
1210 | |
1211 | char *ldbRedisProtocolToHuman_MultiBulk(sds *o, char *reply) { |
1212 | char *p = strchr(reply+1,'\r'); |
1213 | long long mbulklen; |
1214 | int j = 0; |
1215 | |
1216 | string2ll(reply+1,p-reply-1,&mbulklen); |
1217 | p += 2; |
1218 | if (mbulklen == -1) { |
1219 | *o = sdscatlen(*o,"NULL" ,4); |
1220 | return p; |
1221 | } |
1222 | *o = sdscatlen(*o,"[" ,1); |
1223 | for (j = 0; j < mbulklen; j++) { |
1224 | p = ldbRedisProtocolToHuman(o,p); |
1225 | if (j != mbulklen-1) *o = sdscatlen(*o,"," ,1); |
1226 | } |
1227 | *o = sdscatlen(*o,"]" ,1); |
1228 | return p; |
1229 | } |
1230 | |
1231 | char *ldbRedisProtocolToHuman_Set(sds *o, char *reply) { |
1232 | char *p = strchr(reply+1,'\r'); |
1233 | long long mbulklen; |
1234 | int j = 0; |
1235 | |
1236 | string2ll(reply+1,p-reply-1,&mbulklen); |
1237 | p += 2; |
1238 | *o = sdscatlen(*o,"~(" ,2); |
1239 | for (j = 0; j < mbulklen; j++) { |
1240 | p = ldbRedisProtocolToHuman(o,p); |
1241 | if (j != mbulklen-1) *o = sdscatlen(*o,"," ,1); |
1242 | } |
1243 | *o = sdscatlen(*o,")" ,1); |
1244 | return p; |
1245 | } |
1246 | |
1247 | char *ldbRedisProtocolToHuman_Map(sds *o, char *reply) { |
1248 | char *p = strchr(reply+1,'\r'); |
1249 | long long mbulklen; |
1250 | int j = 0; |
1251 | |
1252 | string2ll(reply+1,p-reply-1,&mbulklen); |
1253 | p += 2; |
1254 | *o = sdscatlen(*o,"{" ,1); |
1255 | for (j = 0; j < mbulklen; j++) { |
1256 | p = ldbRedisProtocolToHuman(o,p); |
1257 | *o = sdscatlen(*o," => " ,4); |
1258 | p = ldbRedisProtocolToHuman(o,p); |
1259 | if (j != mbulklen-1) *o = sdscatlen(*o,"," ,1); |
1260 | } |
1261 | *o = sdscatlen(*o,"}" ,1); |
1262 | return p; |
1263 | } |
1264 | |
1265 | char *ldbRedisProtocolToHuman_Null(sds *o, char *reply) { |
1266 | char *p = strchr(reply+1,'\r'); |
1267 | *o = sdscatlen(*o,"(null)" ,6); |
1268 | return p+2; |
1269 | } |
1270 | |
1271 | char *ldbRedisProtocolToHuman_Bool(sds *o, char *reply) { |
1272 | char *p = strchr(reply+1,'\r'); |
1273 | if (reply[1] == 't') |
1274 | *o = sdscatlen(*o,"#true" ,5); |
1275 | else |
1276 | *o = sdscatlen(*o,"#false" ,6); |
1277 | return p+2; |
1278 | } |
1279 | |
1280 | char *ldbRedisProtocolToHuman_Double(sds *o, char *reply) { |
1281 | char *p = strchr(reply+1,'\r'); |
1282 | *o = sdscatlen(*o,"(double) " ,9); |
1283 | *o = sdscatlen(*o,reply+1,p-reply-1); |
1284 | return p+2; |
1285 | } |
1286 | |
1287 | /* Log a Redis reply as debugger output, in a human readable format. |
1288 | * If the resulting string is longer than 'len' plus a few more chars |
1289 | * used as prefix, it gets truncated. */ |
1290 | void ldbLogRedisReply(char *reply) { |
1291 | sds log = sdsnew("<reply> " ); |
1292 | ldbRedisProtocolToHuman(&log,reply); |
1293 | ldbLogWithMaxLen(log); |
1294 | } |
1295 | |
1296 | /* Implements the "print <var>" command of the Lua debugger. It scans for Lua |
1297 | * var "varname" starting from the current stack frame up to the top stack |
1298 | * frame. The first matching variable is printed. */ |
1299 | void ldbPrint(lua_State *lua, char *varname) { |
1300 | lua_Debug ar; |
1301 | |
1302 | int l = 0; /* Stack level. */ |
1303 | while (lua_getstack(lua,l,&ar) != 0) { |
1304 | l++; |
1305 | const char *name; |
1306 | int i = 1; /* Variable index. */ |
1307 | while((name = lua_getlocal(lua,&ar,i)) != NULL) { |
1308 | i++; |
1309 | if (strcmp(varname,name) == 0) { |
1310 | ldbLogStackValue(lua,"<value> " ); |
1311 | lua_pop(lua,1); |
1312 | return; |
1313 | } else { |
1314 | lua_pop(lua,1); /* Discard the var name on the stack. */ |
1315 | } |
1316 | } |
1317 | } |
1318 | |
1319 | /* Let's try with global vars in two selected cases */ |
1320 | if (!strcmp(varname,"ARGV" ) || !strcmp(varname,"KEYS" )) { |
1321 | lua_getglobal(lua, varname); |
1322 | ldbLogStackValue(lua,"<value> " ); |
1323 | lua_pop(lua,1); |
1324 | } else { |
1325 | ldbLog(sdsnew("No such variable." )); |
1326 | } |
1327 | } |
1328 | |
1329 | /* Implements the "print" command (without arguments) of the Lua debugger. |
1330 | * Prints all the variables in the current stack frame. */ |
1331 | void ldbPrintAll(lua_State *lua) { |
1332 | lua_Debug ar; |
1333 | int vars = 0; |
1334 | |
1335 | if (lua_getstack(lua,0,&ar) != 0) { |
1336 | const char *name; |
1337 | int i = 1; /* Variable index. */ |
1338 | while((name = lua_getlocal(lua,&ar,i)) != NULL) { |
1339 | i++; |
1340 | if (!strstr(name,"(*temporary)" )) { |
1341 | sds prefix = sdscatprintf(sdsempty(),"<value> %s = " ,name); |
1342 | ldbLogStackValue(lua,prefix); |
1343 | sdsfree(prefix); |
1344 | vars++; |
1345 | } |
1346 | lua_pop(lua,1); |
1347 | } |
1348 | } |
1349 | |
1350 | if (vars == 0) { |
1351 | ldbLog(sdsnew("No local variables in the current context." )); |
1352 | } |
1353 | } |
1354 | |
1355 | /* Implements the break command to list, add and remove breakpoints. */ |
1356 | void ldbBreak(sds *argv, int argc) { |
1357 | if (argc == 1) { |
1358 | if (ldb.bpcount == 0) { |
1359 | ldbLog(sdsnew("No breakpoints set. Use 'b <line>' to add one." )); |
1360 | return; |
1361 | } else { |
1362 | ldbLog(sdscatfmt(sdsempty(),"%i breakpoints set:" ,ldb.bpcount)); |
1363 | int j; |
1364 | for (j = 0; j < ldb.bpcount; j++) |
1365 | ldbLogSourceLine(ldb.bp[j]); |
1366 | } |
1367 | } else { |
1368 | int j; |
1369 | for (j = 1; j < argc; j++) { |
1370 | char *arg = argv[j]; |
1371 | long line; |
1372 | if (!string2l(arg,sdslen(arg),&line)) { |
1373 | ldbLog(sdscatfmt(sdsempty(),"Invalid argument:'%s'" ,arg)); |
1374 | } else { |
1375 | if (line == 0) { |
1376 | ldb.bpcount = 0; |
1377 | ldbLog(sdsnew("All breakpoints removed." )); |
1378 | } else if (line > 0) { |
1379 | if (ldb.bpcount == LDB_BREAKPOINTS_MAX) { |
1380 | ldbLog(sdsnew("Too many breakpoints set." )); |
1381 | } else if (ldbAddBreakpoint(line)) { |
1382 | ldbList(line,1); |
1383 | } else { |
1384 | ldbLog(sdsnew("Wrong line number." )); |
1385 | } |
1386 | } else if (line < 0) { |
1387 | if (ldbDelBreakpoint(-line)) |
1388 | ldbLog(sdsnew("Breakpoint removed." )); |
1389 | else |
1390 | ldbLog(sdsnew("No breakpoint in the specified line." )); |
1391 | } |
1392 | } |
1393 | } |
1394 | } |
1395 | } |
1396 | |
1397 | /* Implements the Lua debugger "eval" command. It just compiles the user |
1398 | * passed fragment of code and executes it, showing the result left on |
1399 | * the stack. */ |
1400 | void ldbEval(lua_State *lua, sds *argv, int argc) { |
1401 | /* Glue the script together if it is composed of multiple arguments. */ |
1402 | sds code = sdsjoinsds(argv+1,argc-1," " ,1); |
1403 | sds expr = sdscatsds(sdsnew("return " ),code); |
1404 | |
1405 | /* Try to compile it as an expression, prepending "return ". */ |
1406 | if (luaL_loadbuffer(lua,expr,sdslen(expr),"@ldb_eval" )) { |
1407 | lua_pop(lua,1); |
1408 | /* Failed? Try as a statement. */ |
1409 | if (luaL_loadbuffer(lua,code,sdslen(code),"@ldb_eval" )) { |
1410 | ldbLog(sdscatfmt(sdsempty(),"<error> %s" ,lua_tostring(lua,-1))); |
1411 | lua_pop(lua,1); |
1412 | sdsfree(code); |
1413 | sdsfree(expr); |
1414 | return; |
1415 | } |
1416 | } |
1417 | |
1418 | /* Call it. */ |
1419 | sdsfree(code); |
1420 | sdsfree(expr); |
1421 | if (lua_pcall(lua,0,1,0)) { |
1422 | ldbLog(sdscatfmt(sdsempty(),"<error> %s" ,lua_tostring(lua,-1))); |
1423 | lua_pop(lua,1); |
1424 | return; |
1425 | } |
1426 | ldbLogStackValue(lua,"<retval> " ); |
1427 | lua_pop(lua,1); |
1428 | } |
1429 | |
1430 | /* Implement the debugger "redis" command. We use a trick in order to make |
1431 | * the implementation very simple: we just call the Lua redis.call() command |
1432 | * implementation, with ldb.step enabled, so as a side effect the Redis command |
1433 | * and its reply are logged. */ |
1434 | void ldbRedis(lua_State *lua, sds *argv, int argc) { |
1435 | int j; |
1436 | |
1437 | if (!lua_checkstack(lua, argc + 1)) { |
1438 | /* Increase the Lua stack if needed to make sure there is enough room |
1439 | * to push 'argc + 1' elements to the stack. On failure, return error. |
1440 | * Notice that we need, in worst case, 'argc + 1' elements because we push all the arguments |
1441 | * given by the user (without the first argument) and we also push the 'redis' global table and |
1442 | * 'redis.call' function so: |
1443 | * (1 (redis table)) + (1 (redis.call function)) + (argc - 1 (all arguments without the first)) = argc + 1*/ |
1444 | ldbLogRedisReply("max lua stack reached" ); |
1445 | return; |
1446 | } |
1447 | |
1448 | lua_getglobal(lua,"redis" ); |
1449 | lua_pushstring(lua,"call" ); |
1450 | lua_gettable(lua,-2); /* Stack: redis, redis.call */ |
1451 | for (j = 1; j < argc; j++) |
1452 | lua_pushlstring(lua,argv[j],sdslen(argv[j])); |
1453 | ldb.step = 1; /* Force redis.call() to log. */ |
1454 | lua_pcall(lua,argc-1,1,0); /* Stack: redis, result */ |
1455 | ldb.step = 0; /* Disable logging. */ |
1456 | lua_pop(lua,2); /* Discard the result and clean the stack. */ |
1457 | } |
1458 | |
1459 | /* Implements "trace" command of the Lua debugger. It just prints a backtrace |
1460 | * querying Lua starting from the current callframe back to the outer one. */ |
1461 | void ldbTrace(lua_State *lua) { |
1462 | lua_Debug ar; |
1463 | int level = 0; |
1464 | |
1465 | while(lua_getstack(lua,level,&ar)) { |
1466 | lua_getinfo(lua,"Snl" ,&ar); |
1467 | if(strstr(ar.short_src,"user_script" ) != NULL) { |
1468 | ldbLog(sdscatprintf(sdsempty(),"%s %s:" , |
1469 | (level == 0) ? "In" : "From" , |
1470 | ar.name ? ar.name : "top level" )); |
1471 | ldbLogSourceLine(ar.currentline); |
1472 | } |
1473 | level++; |
1474 | } |
1475 | if (level == 0) { |
1476 | ldbLog(sdsnew("<error> Can't retrieve Lua stack." )); |
1477 | } |
1478 | } |
1479 | |
1480 | /* Implements the debugger "maxlen" command. It just queries or sets the |
1481 | * ldb.maxlen variable. */ |
1482 | void ldbMaxlen(sds *argv, int argc) { |
1483 | if (argc == 2) { |
1484 | int newval = atoi(argv[1]); |
1485 | ldb.maxlen_hint_sent = 1; /* User knows about this command. */ |
1486 | if (newval != 0 && newval <= 60) newval = 60; |
1487 | ldb.maxlen = newval; |
1488 | } |
1489 | if (ldb.maxlen) { |
1490 | ldbLog(sdscatprintf(sdsempty(),"<value> replies are truncated at %d bytes." ,(int)ldb.maxlen)); |
1491 | } else { |
1492 | ldbLog(sdscatprintf(sdsempty(),"<value> replies are unlimited." )); |
1493 | } |
1494 | } |
1495 | |
1496 | /* Read debugging commands from client. |
1497 | * Return C_OK if the debugging session is continuing, otherwise |
1498 | * C_ERR if the client closed the connection or is timing out. */ |
1499 | int ldbRepl(lua_State *lua) { |
1500 | sds *argv; |
1501 | int argc; |
1502 | char* err = NULL; |
1503 | |
1504 | /* We continue processing commands until a command that should return |
1505 | * to the Lua interpreter is found. */ |
1506 | while(1) { |
1507 | while((argv = ldbReplParseCommand(&argc, &err)) == NULL) { |
1508 | char buf[1024]; |
1509 | if (err) { |
1510 | luaPushError(lua, err); |
1511 | luaError(lua); |
1512 | } |
1513 | int nread = connRead(ldb.conn,buf,sizeof(buf)); |
1514 | if (nread <= 0) { |
1515 | /* Make sure the script runs without user input since the |
1516 | * client is no longer connected. */ |
1517 | ldb.step = 0; |
1518 | ldb.bpcount = 0; |
1519 | return C_ERR; |
1520 | } |
1521 | ldb.cbuf = sdscatlen(ldb.cbuf,buf,nread); |
1522 | /* after 1M we will exit with an error |
1523 | * so that the client will not blow the memory |
1524 | */ |
1525 | if (sdslen(ldb.cbuf) > 1<<20) { |
1526 | sdsfree(ldb.cbuf); |
1527 | ldb.cbuf = sdsempty(); |
1528 | luaPushError(lua, "max client buffer reached" ); |
1529 | luaError(lua); |
1530 | } |
1531 | } |
1532 | |
1533 | /* Flush the old buffer. */ |
1534 | sdsfree(ldb.cbuf); |
1535 | ldb.cbuf = sdsempty(); |
1536 | |
1537 | /* Execute the command. */ |
1538 | if (!strcasecmp(argv[0],"h" ) || !strcasecmp(argv[0],"help" )) { |
1539 | ldbLog(sdsnew("Redis Lua debugger help:" )); |
1540 | ldbLog(sdsnew("[h]elp Show this help." )); |
1541 | ldbLog(sdsnew("[s]tep Run current line and stop again." )); |
1542 | ldbLog(sdsnew("[n]ext Alias for step." )); |
1543 | ldbLog(sdsnew("[c]ontinue Run till next breakpoint." )); |
1544 | ldbLog(sdsnew("[l]ist List source code around current line." )); |
1545 | ldbLog(sdsnew("[l]ist [line] List source code around [line]." )); |
1546 | ldbLog(sdsnew(" line = 0 means: current position." )); |
1547 | ldbLog(sdsnew("[l]ist [line] [ctx] In this form [ctx] specifies how many lines" )); |
1548 | ldbLog(sdsnew(" to show before/after [line]." )); |
1549 | ldbLog(sdsnew("[w]hole List all source code. Alias for 'list 1 1000000'." )); |
1550 | ldbLog(sdsnew("[p]rint Show all the local variables." )); |
1551 | ldbLog(sdsnew("[p]rint <var> Show the value of the specified variable." )); |
1552 | ldbLog(sdsnew(" Can also show global vars KEYS and ARGV." )); |
1553 | ldbLog(sdsnew("[b]reak Show all breakpoints." )); |
1554 | ldbLog(sdsnew("[b]reak <line> Add a breakpoint to the specified line." )); |
1555 | ldbLog(sdsnew("[b]reak -<line> Remove breakpoint from the specified line." )); |
1556 | ldbLog(sdsnew("[b]reak 0 Remove all breakpoints." )); |
1557 | ldbLog(sdsnew("[t]race Show a backtrace." )); |
1558 | ldbLog(sdsnew("[e]val <code> Execute some Lua code (in a different callframe)." )); |
1559 | ldbLog(sdsnew("[r]edis <cmd> Execute a Redis command." )); |
1560 | ldbLog(sdsnew("[m]axlen [len] Trim logged Redis replies and Lua var dumps to len." )); |
1561 | ldbLog(sdsnew(" Specifying zero as <len> means unlimited." )); |
1562 | ldbLog(sdsnew("[a]bort Stop the execution of the script. In sync" )); |
1563 | ldbLog(sdsnew(" mode dataset changes will be retained." )); |
1564 | ldbLog(sdsnew("" )); |
1565 | ldbLog(sdsnew("Debugger functions you can call from Lua scripts:" )); |
1566 | ldbLog(sdsnew("redis.debug() Produce logs in the debugger console." )); |
1567 | ldbLog(sdsnew("redis.breakpoint() Stop execution like if there was a breakpoint in the" )); |
1568 | ldbLog(sdsnew(" next line of code." )); |
1569 | ldbSendLogs(); |
1570 | } else if (!strcasecmp(argv[0],"s" ) || !strcasecmp(argv[0],"step" ) || |
1571 | !strcasecmp(argv[0],"n" ) || !strcasecmp(argv[0],"next" )) { |
1572 | ldb.step = 1; |
1573 | break; |
1574 | } else if (!strcasecmp(argv[0],"c" ) || !strcasecmp(argv[0],"continue" )){ |
1575 | break; |
1576 | } else if (!strcasecmp(argv[0],"t" ) || !strcasecmp(argv[0],"trace" )) { |
1577 | ldbTrace(lua); |
1578 | ldbSendLogs(); |
1579 | } else if (!strcasecmp(argv[0],"m" ) || !strcasecmp(argv[0],"maxlen" )) { |
1580 | ldbMaxlen(argv,argc); |
1581 | ldbSendLogs(); |
1582 | } else if (!strcasecmp(argv[0],"b" ) || !strcasecmp(argv[0],"break" )) { |
1583 | ldbBreak(argv,argc); |
1584 | ldbSendLogs(); |
1585 | } else if (!strcasecmp(argv[0],"e" ) || !strcasecmp(argv[0],"eval" )) { |
1586 | ldbEval(lua,argv,argc); |
1587 | ldbSendLogs(); |
1588 | } else if (!strcasecmp(argv[0],"a" ) || !strcasecmp(argv[0],"abort" )) { |
1589 | luaPushError(lua, "script aborted for user request" ); |
1590 | luaError(lua); |
1591 | } else if (argc > 1 && |
1592 | (!strcasecmp(argv[0],"r" ) || !strcasecmp(argv[0],"redis" ))) { |
1593 | ldbRedis(lua,argv,argc); |
1594 | ldbSendLogs(); |
1595 | } else if ((!strcasecmp(argv[0],"p" ) || !strcasecmp(argv[0],"print" ))) { |
1596 | if (argc == 2) |
1597 | ldbPrint(lua,argv[1]); |
1598 | else |
1599 | ldbPrintAll(lua); |
1600 | ldbSendLogs(); |
1601 | } else if (!strcasecmp(argv[0],"l" ) || !strcasecmp(argv[0],"list" )){ |
1602 | int around = ldb.currentline, ctx = 5; |
1603 | if (argc > 1) { |
1604 | int num = atoi(argv[1]); |
1605 | if (num > 0) around = num; |
1606 | } |
1607 | if (argc > 2) ctx = atoi(argv[2]); |
1608 | ldbList(around,ctx); |
1609 | ldbSendLogs(); |
1610 | } else if (!strcasecmp(argv[0],"w" ) || !strcasecmp(argv[0],"whole" )){ |
1611 | ldbList(1,1000000); |
1612 | ldbSendLogs(); |
1613 | } else { |
1614 | ldbLog(sdsnew("<error> Unknown Redis Lua debugger command or " |
1615 | "wrong number of arguments." )); |
1616 | ldbSendLogs(); |
1617 | } |
1618 | |
1619 | /* Free the command vector. */ |
1620 | sdsfreesplitres(argv,argc); |
1621 | } |
1622 | |
1623 | /* Free the current command argv if we break inside the while loop. */ |
1624 | sdsfreesplitres(argv,argc); |
1625 | return C_OK; |
1626 | } |
1627 | |
1628 | /* This is the core of our Lua debugger, called each time Lua is about |
1629 | * to start executing a new line. */ |
1630 | void luaLdbLineHook(lua_State *lua, lua_Debug *ar) { |
1631 | scriptRunCtx* rctx = luaGetFromRegistry(lua, REGISTRY_RUN_CTX_NAME); |
1632 | lua_getstack(lua,0,ar); |
1633 | lua_getinfo(lua,"Sl" ,ar); |
1634 | ldb.currentline = ar->currentline; |
1635 | |
1636 | int bp = ldbIsBreakpoint(ldb.currentline) || ldb.luabp; |
1637 | int timeout = 0; |
1638 | |
1639 | /* Events outside our script are not interesting. */ |
1640 | if(strstr(ar->short_src,"user_script" ) == NULL) return; |
1641 | |
1642 | /* Check if a timeout occurred. */ |
1643 | if (ar->event == LUA_HOOKCOUNT && ldb.step == 0 && bp == 0) { |
1644 | mstime_t elapsed = elapsedMs(rctx->start_time); |
1645 | mstime_t timelimit = server.busy_reply_threshold ? |
1646 | server.busy_reply_threshold : 5000; |
1647 | if (elapsed >= timelimit) { |
1648 | timeout = 1; |
1649 | ldb.step = 1; |
1650 | } else { |
1651 | return; /* No timeout, ignore the COUNT event. */ |
1652 | } |
1653 | } |
1654 | |
1655 | if (ldb.step || bp) { |
1656 | char *reason = "step over" ; |
1657 | if (bp) reason = ldb.luabp ? "redis.breakpoint() called" : |
1658 | "break point" ; |
1659 | else if (timeout) reason = "timeout reached, infinite loop?" ; |
1660 | ldb.step = 0; |
1661 | ldb.luabp = 0; |
1662 | ldbLog(sdscatprintf(sdsempty(), |
1663 | "* Stopped at %d, stop reason = %s" , |
1664 | ldb.currentline, reason)); |
1665 | ldbLogSourceLine(ldb.currentline); |
1666 | ldbSendLogs(); |
1667 | if (ldbRepl(lua) == C_ERR && timeout) { |
1668 | /* If the client closed the connection and we have a timeout |
1669 | * connection, let's kill the script otherwise the process |
1670 | * will remain blocked indefinitely. */ |
1671 | luaPushError(lua, "timeout during Lua debugging with client closing connection" ); |
1672 | luaError(lua); |
1673 | } |
1674 | rctx->start_time = getMonotonicUs(); |
1675 | rctx->snapshot_time = mstime(); |
1676 | } |
1677 | } |
1678 | |