/* Define a lua function with the specified function name and body. * The function name musts be a 2 characters long string, since all the * functions we defined in the Lua context are in the form: * * f_<hex sha1 sum> * * On success REDIS_OK is returned, and nothing is left on the Lua stack. * On error REDIS_ERR is returned and an appropriate error is set in the * client context. */ int luaCreateFunction(redisClient *c, lua_State *lua, char *funcname, robj *body) { sds funcdef = sdsempty(); funcdef = sdscat(funcdef,"function "); funcdef = sdscatlen(funcdef,funcname,42); funcdef = sdscatlen(funcdef,"() ",3); funcdef = sdscatlen(funcdef,body->ptr,sdslen(body->ptr)); funcdef = sdscatlen(funcdef," end",4); if (luaL_loadbuffer(lua,funcdef,sdslen(funcdef),"@user_script")) { addReplyErrorFormat(c,"Error compiling script (new function): %s\n", lua_tostring(lua,-1)); lua_pop(lua,1); sdsfree(funcdef); return REDIS_ERR; } sdsfree(funcdef); if (lua_pcall(lua,0,0,0)) { addReplyErrorFormat(c,"Error running script (new function): %s\n", lua_tostring(lua,-1)); lua_pop(lua,1); return REDIS_ERR; } /* We also save a SHA1 -> Original script map in a dictionary * so that we can replicate / write in the AOF all the * EVALSHA commands as EVAL using the original script. */ { int retval = dictAdd(server.lua_scripts, sdsnewlen(funcname+2,40),body); redisAssertWithInfo(c,NULL,retval == DICT_OK); incrRefCount(body); } return REDIS_OK; }
int processCommand(ugClient *c) { if (!strcasecmp(c->argv[0]->ptr,"quit")) { addReply(c,shared.ok); c->flags |= UG_CLOSE_AFTER_REPLY; return UGERR; } if (c->authenticated == 0 && strcasecmp(c->argv[0]->ptr, "auth") != 0) { addReplyError(c,"NOAUTH Authentication required. "); return UGOK; } c->cmd = lookupCommand((sds)c->argv[0]->ptr) ; if (!c->cmd) { addReplyErrorFormat(c,"unknown command '%s'", (char *)c->argv[0]->ptr); return UGOK; } else if (c->cmd->params != -1 && c->argc-1 != c->cmd->params) { addReplyErrorFormat(c,"wrong number of arguments for '%s' command", c->cmd->name); return UGOK; } call(c, 0); /* must has reply */ ugAssert(strlen(c->buf) || listLength(c->reply)); //if (addReply(c, ) == UGERR) { // c->flags |= UG_CLOSE_AFTER_REPLY; // return UGERR; //} return UGOK; }
/* Define a lua function with the specified function name and body. * * 根据给定函数名和代码体(body),创建 Lua 函数。 * * The function name musts be a 2 characters long string, since all the * functions we defined in the Lua context are in the form: * * 所有函数名称的长度都必须大于 2 ,因为我们使用以下格式来创建函数名: * * f_<hex sha1 sum> * * On success REDIS_OK is returned, and nothing is left on the Lua stack. * 创建成功返回 REDIS_OK ,并清除 Lua 栈中的所有资料。 * * On error REDIS_ERR is returned and an appropriate error is set in the * client context. */ int luaCreateFunction(redisClient *c, lua_State *lua, char *funcname, robj *body) { sds funcdef = sdsempty(); // 函数定义 funcdef = sdscat(funcdef,"function "); funcdef = sdscatlen(funcdef,funcname,42); funcdef = sdscatlen(funcdef,"() ",3); funcdef = sdscatlen(funcdef,body->ptr,sdslen(body->ptr)); funcdef = sdscatlen(funcdef," end",4); // 将函数定义载入到 Lua 中(并编译),但不执行该定义 if (luaL_loadbuffer(lua,funcdef,sdslen(funcdef),"@user_script")) { // 如果编译出错,那么返回错误 addReplyErrorFormat(c,"Error compiling script (new function): %s\n", lua_tostring(lua,-1)); lua_pop(lua,1); sdsfree(funcdef); return REDIS_ERR; } sdsfree(funcdef); // 定义函数 if (lua_pcall(lua,0,0,0)) { addReplyErrorFormat(c,"Error running script (new function): %s\n", lua_tostring(lua,-1)); lua_pop(lua,1); return REDIS_ERR; } /* We also save a SHA1 -> Original script map in a dictionary * so that we can replicate / write in the AOF all the * EVALSHA commands as EVAL using the original script. * * 创建一个从 SHA1 映射到原始脚本的字典, * 从而使得脚本可以用于复制,以及写入到 AOF 文件 */ { int retval = dictAdd(server.lua_scripts, // SHA1 值,不包括前缀 f_ sdsnewlen(funcname+2,40), // 代码体 body); redisAssertWithInfo(c,NULL,retval == DICT_OK); incrRefCount(body); } return REDIS_OK; }
/* REPLCONF <option> <value> <option> <value> ... * This command is used by a slave in order to configure the replication * process before starting it with the SYNC command. * * Currently the only use of this command is to communicate to the master * what is the listening port of the Slave redis instance, so that the * master can accurately list slaves and their listening ports in * the INFO output. * * In the future the same command can be used in order to configure * the replication to initiate an incremental replication instead of a * full resync. */ void replconfCommand(redisClient *c) { int j; if ((c->argc % 2) == 0) { /* Number of arguments must be odd to make sure that every * option has a corresponding value. */ addReply(c,shared.syntaxerr); return; } /* Process every option-value pair. */ for (j = 1; j < c->argc; j+=2) { if (!strcasecmp(c->argv[j]->ptr,"listening-port")) { long port; if ((getLongFromObjectOrReply(c,c->argv[j+1], &port,NULL) != REDIS_OK)) return; c->slave_listening_port = port; } else { addReplyErrorFormat(c,"Unrecognized REPLCONF option: %s", (char*)c->argv[j]->ptr); return; } } addReply(c,shared.ok); }
void setGenericCommand(redisClient *c, int flags, robj *key, robj *val, robj *expire, int unit, robj *ok_reply, robj *abort_reply) { long long milliseconds = 0; /* initialized to avoid any harmness warning */ if (expire) { if (getLongLongFromObjectOrReply(c, expire, &milliseconds, NULL) != REDIS_OK) return; if (milliseconds <= 0) { addReplyErrorFormat(c,"invalid expire time in %s",c->cmd->name); return; } if (unit == UNIT_SECONDS) milliseconds *= 1000; } if ((flags & REDIS_SET_NX && lookupKeyWrite(c->db,key) != NULL) || (flags & REDIS_SET_XX && lookupKeyWrite(c->db,key) == NULL)) { addReply(c, abort_reply ? abort_reply : shared.nullbulk); return; } setKey(c->db,key,val); server.dirty++; if (expire) setExpire(c->db,key,mstime()+milliseconds); notifyKeyspaceEvent(REDIS_NOTIFY_STRING,"set",key,c->db->id); if (expire) notifyKeyspaceEvent(REDIS_NOTIFY_GENERIC, "expire",key,c->db->id); addReply(c, ok_reply ? ok_reply : shared.ok); }
int do_delete_event(struct redisClient *c,sds funcname) { redisSrand48(0); server.lua_random_dirty = 0; server.lua_write_dirty = 0; lua_getglobal(server.lua,(char *)funcname); if (lua_isnil(server.lua,1)) { addReplyError(c,"no funcname triggle_scipts in lua"); return 0; } luaTriggleSetGlobalArray(server.lua,"KEYS",c->argv+1,c->argc-1); redisLog(REDIS_NOTICE,"stack: %d",lua_gettop(server.lua)); #ifdef BRIDGE_DEBUG for(int i=0;i<c->argc-1;i++){ redisLog(REDIS_NOTICE,"%s",(c->argv+1)[i]->ptr); } #endif selectDb(server.lua_client,c->db->id); server.lua_time_start = ustime()/1000; server.lua_kill = 0; if (server.lua_time_limit > 0) { lua_sethook(server.lua,luaMaskCountHook,LUA_MASKCOUNT,100000); server.lua_time_start = ustime()/1000; } else { lua_sethook(server.lua,luaMaskCountHook,0,0); } if (lua_pcall(server.lua,0,1,0)) { selectDb(c,server.lua_client->db->id); addReplyErrorFormat(c,"Error running script (call to %s): %s\n", (char*)funcname, lua_tostring(server.lua,-1)); lua_pop(server.lua,1); lua_gc(server.lua,LUA_GCCOLLECT,0); return -1; } selectDb(c,server.lua_client->db->id); // luaReplyToRedisReply(c,server.lua); server.lua_timedout = 0; server.lua_caller = NULL; lua_gc(server.lua,LUA_GCSTEP,1); //for slaves // return 0; }
/* * * slotsinfo [start] [count] * */ void slotsinfoCommand(redisClient *c) { int slots_slot[HASH_SLOTS_SIZE]; int slots_size[HASH_SLOTS_SIZE]; int n = 0, beg = 0, end = HASH_SLOTS_SIZE; if (c->argc >= 2) { if (parse_slot(c, c->argv[1], &beg) != 0) { return; } } if (c->argc >= 3) { int v; if (parse_int(c, c->argv[2], &v) != 0) { return; } if (v < 0) { addReplyErrorFormat(c, "invalid slot count = %d", v); return; } if (beg + v < end) { end = beg + v; } } if (c->argc >= 4) { addReplyErrorFormat(c, "wrong number of arguments for 'slotsinfo' command"); return; } int i; for (i = beg; i < end; i ++) { int s = dictSize(c->db->hash_slots[i]); if (s == 0) { continue; } slots_slot[n] = i; slots_size[n] = s; n ++; } addReplyMultiBulkLen(c, n); for (i = 0; i < n; i ++) { addReplyMultiBulkLen(c, 2); addReplyLongLong(c, slots_slot[i]); addReplyLongLong(c, slots_size[i]); } }
/* LATENCY command implementations. * * LATENCY SAMPLES: return time-latency samples for the specified event. * LATENCY LATEST: return the latest latency for all the events classes. * LATENCY DOCTOR: returns an human readable analysis of instance latency. * LATENCY GRAPH: provide an ASCII graph of the latency of the specified event. */ void latencyCommand(client *c) { struct latencyTimeSeries *ts; if (!strcasecmp(c->argv[1]->ptr,"history") && c->argc == 3) { /* LATENCY HISTORY <event> */ ts = dictFetchValue(server.latency_events,c->argv[2]->ptr); if (ts == NULL) { addReplyMultiBulkLen(c,0); } else { latencyCommandReplyWithSamples(c,ts); } } else if (!strcasecmp(c->argv[1]->ptr,"graph") && c->argc == 3) { /* LATENCY GRAPH <event> */ sds graph; dictEntry *de; char *event; de = dictFind(server.latency_events,c->argv[2]->ptr); if (de == NULL) goto nodataerr; ts = dictGetVal(de); event = dictGetKey(de); graph = latencyCommandGenSparkeline(event,ts); addReplyBulkCString(c,graph); sdsfree(graph); } else if (!strcasecmp(c->argv[1]->ptr,"latest") && c->argc == 2) { /* LATENCY LATEST */ latencyCommandReplyWithLatestEvents(c); } else if (!strcasecmp(c->argv[1]->ptr,"doctor") && c->argc == 2) { /* LATENCY DOCTOR */ sds report = createLatencyReport(); addReplyBulkCBuffer(c,report,sdslen(report)); sdsfree(report); } else if (!strcasecmp(c->argv[1]->ptr,"reset") && c->argc >= 2) { /* LATENCY RESET */ if (c->argc == 2) { addReplyLongLong(c,latencyResetEvent(NULL)); } else { int j, resets = 0; for (j = 2; j < c->argc; j++) resets += latencyResetEvent(c->argv[j]->ptr); addReplyLongLong(c,resets); } } else { addReply(c,shared.syntaxerr); } return; nodataerr: /* Common error when the user asks for an event we have no latency * information about. */ addReplyErrorFormat(c, "No samples available for event '%s'", (char*) c->argv[2]->ptr); }
/* PUBSUB command for Pub/Sub introspection. */ void pubsubCommand(redisClient *c) { // 处理PUBSUB CHANNELS [pattern]命令 if (!strcasecmp(c->argv[1]->ptr,"channels") && (c->argc == 2 || c->argc ==3)) { /* PUBSUB CHANNELS [<pattern>] */ // 获取pattern参数,如果没有则为NULL sds pat = (c->argc == 2) ? NULL : c->argv[2]->ptr; dictIterator *di = dictGetIterator(server.pubsub_channels); dictEntry *de; long mblen = 0; void *replylen; replylen = addDeferredMultiBulkLength(c); // 遍历server.pubsub_channels字典 while((de = dictNext(di)) != NULL) { // 取出当前频道channel robj *cobj = dictGetKey(de); sds channel = cobj->ptr; // 如果没有给定pattern参数,则打印出所有频道 // 如果给定pattern参数,则打印出与pattern参数相匹配的频道 if (!pat || stringmatchlen(pat, sdslen(pat), channel, sdslen(channel),0)) { addReplyBulk(c,cobj); mblen++; } } dictReleaseIterator(di); setDeferredMultiBulkLength(c,replylen,mblen); } // 处理PUBSUB NUMSUB [Channel_1 ... Channel_N]命令 else if (!strcasecmp(c->argv[1]->ptr,"numsub") && c->argc >= 2) { /* PUBSUB NUMSUB [Channel_1 ... Channel_N] */ int j; addReplyMultiBulkLen(c,(c->argc-2)*2); for (j = 2; j < c->argc; j++) { list *l = dictFetchValue(server.pubsub_channels,c->argv[j]); addReplyBulk(c,c->argv[j]); addReplyLongLong(c,l ? listLength(l) : 0); } } // 处理PUBSUB NUMPA命令 else if (!strcasecmp(c->argv[1]->ptr,"numpat") && c->argc == 2) { /* PUBSUB NUMPAT */ addReplyLongLong(c,listLength(server.pubsub_patterns)); } else { addReplyErrorFormat(c, "Unknown PUBSUB subcommand or wrong number of arguments for '%s'", (char*)c->argv[1]->ptr); } }
//TODO webserver_mode, webserver_index_function, // webserver_whitelist_address, webserver_whitelist_netmask, // sqlslaveof int DXDB_configSetCommand(cli *c, robj *o) { if (!strcasecmp(c->argv[2]->ptr,"luacronfunc")) { if (server.alc.LuaCronFunc) zfree(server.alc.LuaCronFunc); server.alc.LuaCronFunc = zstrdup(o->ptr); return 0; } else if (!strcasecmp(c->argv[2]->ptr, "lua_output_start")) { if (server.alc.OutputLuaFunc_Start) { zfree(server.alc.OutputLuaFunc_Start); } server.alc.OutputLuaFunc_Start = zstrdup(o->ptr); return 0; } else if (!strcasecmp(c->argv[2]->ptr, "lua_output_cnames")) { if (server.alc.OutputLuaFunc_Cnames) { zfree(server.alc.OutputLuaFunc_Cnames); } server.alc.OutputLuaFunc_Cnames = zstrdup(o->ptr); return 0; } else if (!strcasecmp(c->argv[2]->ptr, "lua_output_row")) { if (server.alc.OutputLuaFunc_Row) zfree(server.alc.OutputLuaFunc_Row); server.alc.OutputLuaFunc_Row = zstrdup(o->ptr); return 0; } else if (!strcasecmp(c->argv[2]->ptr, "rest_api_mode")) { int yn = yesnotoi(o->ptr); if (yn == -1) goto badfmt; server.alc.RestAPIMode = yn ? 1 : -1; return 0; } else if (!strcasecmp(c->argv[2]->ptr, "outputmode")) { if (!strcasecmp(o->ptr, "embedded")) { server.alc.OutputMode = OUTPUT_EMBEDDED; } else if (!strcasecmp(o->ptr, "pure_redis")) { server.alc.OutputMode = OUTPUT_PURE_REDIS; } else if (!strcasecmp(o->ptr, "normal")) { server.alc.OutputMode = OUTPUT_NORMAL; } else if (!strcasecmp(o->ptr, "lua")) { if (!server.alc.OutputLuaFunc_Start || !server.alc.OutputLuaFunc_Cnames || !server.alc.OutputLuaFunc_Row) { sds err = sdsnew("-ERR: OUTPUTMODE lua requires " \ "[lua_output_start, lua_output_cnames, " \ "lua_output_row]\r\n"); addReplySds(c, err); decrRefCount(o); return -1; } server.alc.OutputMode = OUTPUT_LUA; } else { addReplySds(c,sdscatprintf(sdsempty(), "-ERR OUTPUTMODE: [EMBEDDED|PURE_REDIS|LUA|NORMAL] not: %s\r\n", (char *)o->ptr)); decrRefCount(o); return -1; } return 0; } return 1; badfmt: /* Bad format errors */ addReplyErrorFormat(c,"Invalid argument '%s' for CONFIG SET '%s'", (char*)o->ptr, (char*)c->argv[2]->ptr); return -1; }
/* Don't let people use keys starting with a valid container box value */ bool validateKeyFormatAndReply(redisClient *c, sds key) { if (isValidBoxKeyPrefix(key[0])) { addReplyErrorFormat( c, "Invalid first character in key: [%c] (binary [%s])", key[0], bb(key[0])); return false; } return true; }
void debugCommand(client *c) { if (!strcasecmp(c->argv[1]->ptr,"segfault")) { *((char*)-1) = 'x'; } else if (!strcasecmp(c->argv[1]->ptr,"oom")) { void *ptr = zmalloc(ULONG_MAX); /* Should trigger an out of memory. */ zfree(ptr); addReply(c,shared.ok); } else if (!strcasecmp(c->argv[1]->ptr,"assert")) { if (c->argc >= 3) c->argv[2] = tryObjectEncoding(c->argv[2]); serverAssertWithInfo(c,c->argv[0],1 == 2); } else if (!strcasecmp(c->argv[1]->ptr,"flushall")) { flushServerData(); addReply(c,shared.ok); } else if (!strcasecmp(c->argv[1]->ptr,"loadaof")) { flushServerData(); if (loadAppendOnlyFile(server.aof_filename) != C_OK) { addReply(c,shared.err); return; } serverLog(LL_WARNING,"Append Only File loaded by DEBUG LOADAOF"); addReply(c,shared.ok); } else if (!strcasecmp(c->argv[1]->ptr,"sleep") && c->argc == 3) { double dtime = strtod(c->argv[2]->ptr,NULL); long long utime = dtime*1000000; struct timespec tv; tv.tv_sec = utime / 1000000; tv.tv_nsec = (utime % 1000000) * 1000; nanosleep(&tv, NULL); addReply(c,shared.ok); } else if (!strcasecmp(c->argv[1]->ptr,"error") && c->argc == 3) { sds errstr = sdsnewlen("-",1); errstr = sdscatsds(errstr,c->argv[2]->ptr); errstr = sdsmapchars(errstr,"\n\r"," ",2); /* no newlines in errors. */ errstr = sdscatlen(errstr,"\r\n",2); addReplySds(c,errstr); } else if (!strcasecmp(c->argv[1]->ptr,"structsize") && c->argc == 2) { sds sizes = sdsempty(); sizes = sdscatprintf(sizes,"bits:%d ",(sizeof(void*) == 8)?64:32); sizes = sdscatprintf(sizes,"job:%d ", (int)sizeof(job)); sizes = sdscatprintf(sizes,"queue:%d ", (int)sizeof(queue)); sizes = sdscatprintf(sizes,"robj:%d ",(int)sizeof(robj)); sizes = sdscatprintf(sizes,"dictentry:%d ",(int)sizeof(dictEntry)); sizes = sdscatprintf(sizes,"sdshdr5:%d ",(int)sizeof(struct sdshdr5)); sizes = sdscatprintf(sizes,"sdshdr8:%d ",(int)sizeof(struct sdshdr8)); sizes = sdscatprintf(sizes,"sdshdr16:%d ",(int)sizeof(struct sdshdr16)); sizes = sdscatprintf(sizes,"sdshdr32:%d ",(int)sizeof(struct sdshdr32)); sizes = sdscatprintf(sizes,"sdshdr64:%d ",(int)sizeof(struct sdshdr64)); addReplyBulkSds(c,sizes); } else { addReplyErrorFormat(c, "Unknown DEBUG subcommand or wrong number of arguments for '%s'", (char*)c->argv[1]->ptr); } }
static int parse_slot(redisClient *c, robj *obj, int *p) { int v; if (parse_int(c, obj, &v) != 0) { return -1; } if (v < 0 || v >= HASH_SLOTS_SIZE) { addReplyErrorFormat(c, "invalid slot number = %d", v); return -1; } *p = v; return 0; }
static int parse_timeout(redisClient *c, robj *obj, int *p) { int v; if (parse_int(c, obj, &v) != 0) { return -1; } if (v < 0) { addReplyErrorFormat(c, "invalid timeout = %d", v); return -1; } *p = (v == 0) ? 100 : v; return 0; }
/* * * slotsdel slot1 [slot2 ...] * */ void slotsdelCommand(redisClient *c) { int slots_slot[HASH_SLOTS_SIZE]; int n = 0; if (c->argc <= 1) { addReplyErrorFormat(c, "wrong number of arguments for 'slotsdel' command"); return; } int i; for (i = 1; i < c->argc; i ++) { int slot; if (parse_slot(c, c->argv[i], &slot) != 0) { return; } slots_slot[n] = slot; n ++; } for (i = 0; i < n; i ++) { dict *d = c->db->hash_slots[slots_slot[i]]; int s = dictSize(d); if (s == 0) { continue; } list *l = listCreate(); listSetFreeMethod(l, decrRefCountVoid); unsigned long cursor = 0; do { cursor = dictScan(d, cursor, slotsScanSdsKeyCallback, l); } while (cursor != 0); while (1) { listNode *head = listFirst(l); if (head == NULL) { break; } robj *key = listNodeValue(head); robj *keys[] = {key}; slotsremove(c, keys, 1, 0); listDelNode(l, head); } listRelease(l); } addReplyMultiBulkLen(c, n); for (i = 0; i < n; i ++) { int n = slots_slot[i]; int s = dictSize(c->db->hash_slots[n]); addReplyMultiBulkLen(c, 2); addReplyLongLong(c, n); addReplyLongLong(c, s); } }
/* * PUBSUB 命令, 内省命令 */ void pubsubCommand(redisClient *c) { if (!strcasecmp(c->argv[1]->ptr,"channels") && (c->argc == 2 || c->argc ==3)) //列出当前活跃的频道,每个频道至少有一个订阅者,订阅模式的客户端不计算在内 { /* PUBSUB CHANNELS [<pattern>] */ sds pat = (c->argc == 2) ? NULL : c->argv[2]->ptr; dictIterator *di = dictGetIterator(server.pubsub_channels); dictEntry *de; long mblen = 0; void *replylen; replylen = addDeferredMultiBulkLength(c); while((de = dictNext(di)) != NULL) { robj *cobj = dictGetKey(de); sds channel = cobj->ptr; if (!pat || stringmatchlen(pat, sdslen(pat), channel, sdslen(channel),0)) { addReplyBulk(c,cobj); mblen++; } } dictReleaseIterator(di); setDeferredMultiBulkLength(c,replylen,mblen); } else if (!strcasecmp(c->argv[1]->ptr,"numsub") && c->argc >= 2) { //返回给定频道的订阅者数量,订阅模式的客户端不计算在内 /* PUBSUB NUMSUB [Channel_1 ... Channel_N] */ int j; addReplyMultiBulkLen(c,(c->argc-2)*2); for (j = 2; j < c->argc; j++) { list *l = dictFetchValue(server.pubsub_channels,c->argv[j]); addReplyBulk(c,c->argv[j]); addReplyLongLong(c,l ? listLength(l) : 0); } } else if (!strcasecmp(c->argv[1]->ptr,"numpat") && c->argc == 2) { //返回订阅模式的数量 /* PUBSUB NUMPAT */ addReplyLongLong(c,listLength(server.pubsub_patterns)); } else { addReplyErrorFormat(c, "Unknown PUBSUB subcommand or wrong number of arguments for '%s'", (char*)c->argv[1]->ptr); } }
/* COMMAND <subcommand> <args> */ void commandCommand(client *c) { dictIterator *di; dictEntry *de; if (c->argc == 1) { addReplyMultiBulkLen(c, dictSize(server.commands)); di = dictGetIterator(server.commands); while ((de = dictNext(di)) != NULL) { addReplyCommand(c, dictGetVal(de)); } dictReleaseIterator(di); } else if (!strcasecmp(c->argv[1]->ptr, "info")) { int i; addReplyMultiBulkLen(c, c->argc-2); for (i = 2; i < c->argc; i++) { addReplyCommand(c, dictFetchValue(server.commands, c->argv[i]->ptr)); } } else if (!strcasecmp(c->argv[1]->ptr, "count") && c->argc == 2) { addReplyLongLong(c, dictSize(server.commands)); } else if (!strcasecmp(c->argv[1]->ptr,"getkeys") && c->argc >= 3) { struct redisCommand *cmd = lookupCommand(c->argv[2]->ptr); int *keys, numkeys, j; if (!cmd) { addReplyErrorFormat(c,"Invalid command specified"); return; } else if ((cmd->arity > 0 && cmd->arity != c->argc-2) || ((c->argc-2) < -cmd->arity)) { addReplyError(c,"Invalid number of arguments specified for command"); return; } keys = getKeysFromCommand(cmd,c->argv+2,c->argc-2,&numkeys); addReplyMultiBulkLen(c,numkeys); for (j = 0; j < numkeys; j++) addReplyBulk(c,c->argv[keys[j]+2]); getKeysFreeResult(keys); } else { addReplyError(c, "Unknown subcommand or wrong number of arguments."); return; } }
void getCommand(client *c) { counter *cntr; cntr = counterLookup(c->argv[1]->ptr); if (cntr == NULL) { if (c->argc == 2) { addReplyString(c,OBJ_SHARED_0STR,sizeof(OBJ_SHARED_0STR)-1); } else if (!strcasecmp(c->argv[2]->ptr,"state")) { addReplyMultiBulkLen(c,2); addReplyString(c,OBJ_SHARED_0STR,sizeof(OBJ_SHARED_0STR)-1); if (server.cluster->failing_nodes_count > 0) { addReplyString(c, OBJ_SHARED_INCONSISTENT, sizeof(OBJ_SHARED_INCONSISTENT)-1); } else { addReplyString(c, OBJ_SHARED_CONSISTENT, sizeof(OBJ_SHARED_CONSISTENT)-1); } } return; } /* Do we need to recalculate the cached response? */ if (cntr->rlen == 0) { counterCacheResponse(cntr); } if (c->argc == 2) { addReplyString(c,cntr->rbuf,cntr->rlen); } else if (!strcasecmp(c->argv[2]->ptr,"state")) { addReplyMultiBulkLen(c,2); addReplyString(c,cntr->rbuf,cntr->rlen); if (server.cluster->failing_nodes_count > 0) { addReplyString(c, OBJ_SHARED_INCONSISTENT, sizeof(OBJ_SHARED_INCONSISTENT)-1); } else { addReplyString(c, OBJ_SHARED_CONSISTENT, sizeof(OBJ_SHARED_CONSISTENT)-1); } } else { addReplyErrorFormat(c, "Unknown GET option '%s'", (char*)c->argv[2]->ptr); } }
/* The PING command. It works in a different way if the client is in * in Pub/Sub mode. */ void pingCommand(client *c) { /* The command takes zero or one arguments. */ if (c->argc > 2) { addReplyErrorFormat(c,"wrong number of arguments for '%s' command", c->cmd->name); return; } if (c->flags & CLIENT_PUBSUB) { addReply(c,shared.mbulkhdr[2]); addReplyBulkCBuffer(c,"pong",4); if (c->argc == 1) addReplyBulkCBuffer(c,"",0); else addReplyBulk(c,c->argv[1]); } else { if (c->argc == 1) addReply(c,shared.pong); else addReplyBulk(c,c->argv[1]); } }
static int slotsmgrt_get_socket(redisClient *c, sds host, sds port, int timeout) { sds name = sdsempty(); name = sdscatlen(name, host, sdslen(host)); name = sdscatlen(name, ":", 1); name = sdscatlen(name, port, sdslen(port)); slotsmgrt_sockfd *pfd = dictFetchValue(server.slotsmgrt_cached_sockfds, name); if (pfd != NULL) { sdsfree(name); pfd->lasttime = server.unixtime; return pfd->fd; } int fd = anetTcpNonBlockConnect(server.neterr, host, atoi(port)); if (fd == -1) { redisLog(REDIS_WARNING, "slotsmgrt: connect to target %s:%s, error = '%s'", host, port, server.neterr); sdsfree(name); addReplyErrorFormat(c,"Can't connect to target node: %s", server.neterr); return -1; } anetEnableTcpNoDelay(server.neterr, fd); if ((aeWait(fd, AE_WRITABLE, timeout) & AE_WRITABLE) == 0) { redisLog(REDIS_WARNING, "slotsmgrt: connect to target %s:%s, aewait error = '%s'", host, port, server.neterr); sdsfree(name); close(fd); addReplySds(c, sdsnew("-IOERR error or timeout connecting to the client\r\n")); return -1; } redisLog(REDIS_WARNING, "slotsmgrt: connect to target %s:%s", host, port); pfd = zmalloc(sizeof(*pfd)); pfd->fd = fd; pfd->lasttime = server.unixtime; dictAdd(server.slotsmgrt_cached_sockfds, name, pfd); return fd; }
int processLineBuffer(ugClient *c) { char *newline = strstr(c->querybuf, "\r\n"); int argc, j; sds *argv; size_t querylen; /* Nothing to do without a \r\n */ if (newline == NULL) { if (sdslen(c->querybuf) > UG_INLINE_MAX_SIZE) { addReplyErrorFormat(c, "Protocol error: too big inline request"); setProtocolError(c,0); } return UGERR; } /* Split the input buffer up to the \r\n */ querylen = newline-(c->querybuf); argv = sdssplitlen(c->querybuf,(int)querylen," ",1,&argc); /* Leave data after the first line of the query in the buffer */ c->querybuf = sdsrange(c->querybuf,(int)(querylen+2),-1); /* Setup argv array on client structure */ if (c->argv) zfree(c->argv); c->argv = zmalloc(sizeof(robj *)*argc); /* Create redis objects for all arguments. */ for (c->argc = 0, j = 0; j < argc; j++) { if (sdslen(argv[j])) { c->argv[c->argc] = createObject(UG_STRING,argv[j]); c->argc++; } else { sdsfree(argv[j]); } } zfree(argv); return UGOK; }
void lsCommand(redisClient* c) { DIR* pstDir = opendir((const char*)c->argv[1]->ptr); if (pstDir == NULL) { addReplyErrorFormat(c, "opendir fail, %s", strerror(errno)); return; } list* dir = listCreate(); struct dirent* pstDirent = NULL; while ((pstDirent = readdir(pstDir)) != NULL) { if(strcmp(pstDirent->d_name, ".") == 0 || strcmp(pstDirent->d_name, "..") == 0) continue; sds path = sdsdup(c->argv[1]->ptr); size_t len = sdslen(path); if (path[len - 1] != '/') path = sdscat(path, "/"); path = sdscat(path, pstDirent->d_name); listAddNodeTail(dir, path); } closedir(pstDir); addReplyMultiBulkLen(c, listLength(dir)); listIter* iter = listGetIterator(dir, AL_START_HEAD); listNode* node = NULL; while ((node = listNext(iter))) { addReplyBulkCString(c, node->value); sdsfree(node->value); listDelNode(dir, node); } listRelease(dir); }
/* If this function gets called we already read a whole * command, arguments are in the client argv/argc fields. * processCommand() execute the command or prepare the * server for a bulk read from the client. * * If VR_OK is returned the client is still alive and valid and * other operations can be performed by the caller. Otherwise * if VR_ERROR is returned the client was destroyed (i.e. after QUIT). */ int processCommand(client *c) { long long maxmemory; /* The QUIT command is handled separately. Normal command procs will * go through checking for replication and QUIT will cause trouble * when FORCE_REPLICATION is enabled and would be implemented in * a regular command proc. */ if (!strcasecmp(c->argv[0]->ptr,"quit")) { addReply(c,shared.ok); c->flags |= CLIENT_CLOSE_AFTER_REPLY; return VR_ERROR; } /* Now lookup the command and check ASAP about trivial error conditions * such as wrong arity, bad command name and so forth. */ c->cmd = c->lastcmd = lookupCommand(c->argv[0]->ptr); if (!c->cmd) { flagTransaction(c); addReplyErrorFormat(c,"unknown command '%s'", (char*)c->argv[0]->ptr); return VR_OK; } else if ((c->cmd->arity > 0 && c->cmd->arity != c->argc) || (c->argc < -c->cmd->arity)) { flagTransaction(c); addReplyErrorFormat(c,"wrong number of arguments for '%s' command", c->cmd->name); return VR_OK; } /* Check if the user is authenticated */ if (server.requirepass && !c->authenticated && c->cmd->proc != authCommand) { flagTransaction(c); addReply(c,shared.noautherr); return VR_OK; } /* Handle the maxmemory directive. * * First we try to free some memory if possible (if there are volatile * keys in the dataset). If there are not the only thing we can do * is returning an error. */ conf_server_get(CONFIG_SOPN_MAXMEMORY,&maxmemory); if (maxmemory) { int retval = freeMemoryIfNeeded(c->vel); /* freeMemoryIfNeeded may flush slave output buffers. This may result * into a slave, that may be the active client, to be freed. */ if (c->vel->current_client == NULL) return VR_ERROR; /* It was impossible to free enough memory, and the command the client * is trying to execute is denied during OOM conditions? Error. */ if ((c->cmd->flags & CMD_DENYOOM) && retval == VR_ERROR) { flagTransaction(c); addReply(c, shared.oomerr); return VR_OK; } } /* Don't accept write commands if there are problems persisting on disk * and if this is a master instance. */ if (((server.stop_writes_on_bgsave_err && server.saveparamslen > 0 && server.lastbgsave_status == VR_ERROR) || server.aof_last_write_status == VR_ERROR) && repl.masterhost == NULL && (c->cmd->flags & CMD_WRITE || c->cmd->proc == pingCommand)) { flagTransaction(c); if (server.aof_last_write_status == VR_OK) addReply(c, shared.bgsaveerr); else addReplySds(c, sdscatprintf(sdsempty(), "-MISCONF Errors writing to the AOF file: %s\r\n", strerror(server.aof_last_write_errno))); return VR_OK; } /* Don't accept write commands if there are not enough good slaves and * user configured the min-slaves-to-write option. */ if (repl.masterhost == NULL && repl.repl_min_slaves_to_write && repl.repl_min_slaves_max_lag && c->cmd->flags & CMD_WRITE && repl.repl_good_slaves_count < repl.repl_min_slaves_to_write) { flagTransaction(c); addReply(c, shared.noreplicaserr); return VR_OK; } /* Don't accept write commands if this is a read only slave. But * accept write commands if this is our master. */ if (repl.masterhost && repl.repl_slave_ro && !(c->flags & CLIENT_MASTER) && c->cmd->flags & CMD_WRITE) { addReply(c, shared.roslaveerr); return VR_OK; } /* Only allow SUBSCRIBE and UNSUBSCRIBE in the context of Pub/Sub */ if (c->flags & CLIENT_PUBSUB && c->cmd->proc != pingCommand && c->cmd->proc != subscribeCommand && c->cmd->proc != unsubscribeCommand && c->cmd->proc != psubscribeCommand && c->cmd->proc != punsubscribeCommand) { addReplyError(c,"only (P)SUBSCRIBE / (P)UNSUBSCRIBE / PING / QUIT allowed in this context"); return VR_OK; } /* Only allow INFO and SLAVEOF when slave-serve-stale-data is no and * we are a slave with a broken link with master. */ if (repl.masterhost && repl.repl_state != REPL_STATE_CONNECTED && repl.repl_serve_stale_data == 0 && !(c->cmd->flags & CMD_STALE)) { flagTransaction(c); addReply(c, shared.masterdownerr); return VR_OK; } /* Loading DB? Return an error if the command has not the * CMD_LOADING flag. */ if (server.loading && !(c->cmd->flags & CMD_LOADING)) { addReply(c, shared.loadingerr); return VR_OK; } /* Lua script too slow? Only allow a limited number of commands. */ if (server.lua_timedout && c->cmd->proc != authCommand && c->cmd->proc != replconfCommand && !(c->cmd->proc == shutdownCommand && c->argc == 2 && tolower(((char*)c->argv[1]->ptr)[0]) == 'n') && !(c->cmd->proc == scriptCommand && c->argc == 2 && tolower(((char*)c->argv[1]->ptr)[0]) == 'k')) { flagTransaction(c); addReply(c, shared.slowscripterr); return VR_OK; } /* Exec the command */ if (c->flags & CLIENT_MULTI && c->cmd->proc != execCommand && c->cmd->proc != discardCommand && c->cmd->proc != multiCommand && c->cmd->proc != watchCommand) { queueMultiCommand(c); addReply(c,shared.queued); } else { call(c,CMD_CALL_FULL); c->woff = repl.master_repl_offset; if (listLength(server.ready_keys)) handleClientsBlockedOnLists(); } return VR_OK; }
void triggleGenericCommand(redisClient *c, int nx, robj *db_id, robj *key_pattern,robj *event_type, robj *script_source) { redisLog(REDIS_NOTICE,"dbid: %s keypattern: %s script_source: %s ",db_id->ptr,key_pattern->ptr,script_source->ptr); int id = atoi(db_id->ptr); int int_event=process_trigglecmd(event_type->ptr); if(int_event==-1) { addReplyError(c,"undefine event in redis triggle"); return; } //redisLog(REDIS_NOTICE,"get event:%d for: %s",int_event,event_type->ptr); if(id<0||id>server.dbnum) { addReplyError(c,"wrong dbid for triggle"); return; } // redisLog(REDIS_NOTICE,"add into stack: %d",lua_gettop(server.lua)); /*lua_check*/ sds funcdef = sdsempty(); funcdef = sdscat(funcdef,"function "); funcdef = sdscatlen(funcdef,key_pattern->ptr,sdslen(key_pattern->ptr)); funcdef = sdscatlen(funcdef,"() ",3); funcdef = sdscatlen(funcdef,script_source->ptr,sdslen(script_source->ptr)); funcdef = sdscatlen(funcdef," end",4); //redisLog(REDIS_NOTICE,"script function:%s",funcdef); if (luaL_loadbuffer(server.lua,funcdef,sdslen(funcdef),"@user_script")) { addReplyErrorFormat(c,"Error compiling script (new function): %s\n", lua_tostring(server.lua,-1)); lua_pop(server.lua,1); sdsfree(funcdef); return ; } sdsfree(funcdef); //redisLog(REDIS_NOTICE,"add load buffer stack: %d",lua_gettop(server.lua)); if (lua_pcall(server.lua,0,0,0)) { addReplyErrorFormat(c,"Error running script (new function): %s\n", lua_tostring(server.lua,-1)); lua_pop(server.lua,1); return ; } //redisLog(REDIS_NOTICE,"run buffer stack: %d",lua_gettop(server.lua)); struct bridge_db_triggle_t *tmptrg=zmalloc(sizeof(struct bridge_db_triggle_t)); tmptrg->dbid=id; tmptrg->event=int_event; tmptrg->lua_scripts=script_source; incrRefCount(script_source); sds copy=sdsdup(key_pattern->ptr); dictAdd(server.bridge_db.triggle_scipts[id],copy,tmptrg); addReply(c, nx ? shared.cone : shared.ok); }
/* * * slotsrestore key ttl val [key ttl val ...] * */ void slotsrestoreCommand(redisClient *c) { if (c->argc < 4 || (c->argc - 1) % 3 != 0) { addReplyErrorFormat(c, "wrong number of arguments for 'slotsrestore' command"); return; } int n = (c->argc - 1) / 3; long long *ttls = zmalloc(sizeof(long long) * n); robj **vals = zmalloc(sizeof(robj *) * n); for (int i = 0; i < n; i ++) { vals[i] = NULL; } for (int i = 0; i < n; i ++) { robj *key = c->argv[i * 3 + 1]; robj *ttl = c->argv[i * 3 + 2]; robj *val = c->argv[i * 3 + 3]; if (lookupKeyWrite(c->db, key) != NULL) { redisLog(REDIS_WARNING, "slotsrestore: slot = %d, key = '%s' already exists", slots_num(key->ptr, NULL), (char *)key->ptr); } if (getLongLongFromObjectOrReply(c, ttl, &ttls[i], NULL) != REDIS_OK) { goto cleanup; } else if (ttls[i] < 0) { addReplyError(c, "invalid ttl value, must be >= 0"); goto cleanup; } rio payload; int type; if (verifyDumpPayload(val->ptr, sdslen(val->ptr)) != REDIS_OK) { addReplyError(c, "dump payload version or checksum are wrong"); goto cleanup; } rioInitWithBuffer(&payload, val->ptr); if (((type = rdbLoadObjectType(&payload)) == -1) || ((vals[i] = rdbLoadObject(type, &payload)) == NULL)) { addReplyError(c, "bad data format"); goto cleanup; } } for (int i = 0; i < n; i ++) { robj *key = c->argv[i * 3 + 1]; long long ttl = ttls[i]; robj *val = vals[i]; dbDelete(c->db, key); dbAdd(c->db, key, val); incrRefCount(val); if (ttl) { setExpire(c->db, key, mstime() + ttl); } signalModifiedKey(c->db, key); server.dirty ++; } addReply(c, shared.ok); cleanup: for (int i = 0; i < n; i ++) { if (vals[i] != NULL) { decrRefCount(vals[i]); } } zfree(vals); zfree(ttls); }
void evalGenericCommand(redisClient *c, int evalsha) { lua_State *lua = server.lua; char funcname[43]; long long numkeys; int delhook = 0, err; // 随机数的种子,在产生哈希值的时候会用到 /* We want the same PRNG sequence at every call so that our PRNG is * not affected by external state. */ redisSrand48(0); /* We set this flag to zero to remember that so far no random command * was called. This way we can allow the user to call commands like * SRANDMEMBER or RANDOMKEY from Lua scripts as far as no write command * is called (otherwise the replication and AOF would end with non * deterministic sequences). * * Thanks to this flag we'll raise an error every time a write command * is called after a random command was used. */ server.lua_random_dirty = 0; server.lua_write_dirty = 0; // 检查参数的有效性 /* Get the number of arguments that are keys */ if (getLongLongFromObjectOrReply(c,c->argv[2],&numkeys,NULL) != REDIS_OK) return; if (numkeys > (c->argc - 3)) { addReplyError(c,"Number of keys can't be greater than number of args"); return; } // 函数名以 f_ 开头 /* We obtain the script SHA1, then check if this function is already * defined into the Lua state */ funcname[0] = 'f'; funcname[1] = '_'; // 如果没有哈希值,需要计算 lua 脚本的哈希值 if (!evalsha) { // 计算哈希值,会放入到 SHA1 -> lua_script 哈希表中 // c->argv[1]->ptr 是用户指定的 lua 脚本 // sha1hex() 产生的哈希值存在 funcname 中 /* Hash the code if this is an EVAL call */ sha1hex(funcname+2,c->argv[1]->ptr,sdslen(c->argv[1]->ptr)); } else { // 用户自己指定了哈希值 /* We already have the SHA if it is a EVALSHA */ int j; char *sha = c->argv[1]->ptr; for (j = 0; j < 40; j++) funcname[j+2] = tolower(sha[j]); funcname[42] = '\0'; } // 将错误处理函数入栈 // lua_getglobal() 会将读取指定的全局变量,且将其入栈 /* Push the pcall error handler function on the stack. */ lua_getglobal(lua, "__redis__err__handler"); /* Try to lookup the Lua function */ // 在 lua 中查找是否注册了此函数。这一句尝试将 funcname 入栈 lua_getglobal(lua, funcname); if (lua_isnil(lua,-1)) { // funcname 在 lua 中不存在 // 将 nil 出栈 lua_pop(lua,1); /* remove the nil from the stack */ // 已经确定 funcname 在 lua 中没有定义,需要创建 /* Function not defined... let's define it if we have the * body of the function. If this is an EVALSHA call we can just * return an error. */ if (evalsha) { lua_pop(lua,1); /* remove the error handler from the stack. */ addReply(c, shared.noscripterr); return; } // 创建 lua 函数 funcname // c->argv[1] 指向用户指定的 lua 脚本 if (luaCreateFunction(c,lua,funcname,c->argv[1]) == REDIS_ERR) { lua_pop(lua,1); /* remove the error handler from the stack. */ /* The error is sent to the client by luaCreateFunction() * itself when it returns REDIS_ERR. */ return; } // 现在 lua 中已经有 funcname 这个全局变量了,将其读取并入栈, // 准备调用 /* Now the following is guaranteed to return non nil */ lua_getglobal(lua, funcname); redisAssert(!lua_isnil(lua,-1)); } // 设置参数,包括键和值 /* Populate the argv and keys table accordingly to the arguments that * EVAL received. */ luaSetGlobalArray(lua,"KEYS",c->argv+3,numkeys); luaSetGlobalArray(lua,"ARGV",c->argv+3+numkeys,c->argc-3-numkeys); // 选择数据集,lua_client 有专用的数据集 /* Select the right DB in the context of the Lua client */ selectDb(server.lua_client,c->db->id); // 设置超时回调函数,以在 lua 脚本执行过长时间的时候停止脚本的运行 /* Set a hook in order to be able to stop the script execution if it * is running for too much time. * We set the hook only if the time limit is enabled as the hook will * make the Lua script execution slower. */ server.lua_caller = c; server.lua_time_start = ustime()/1000; server.lua_kill = 0; if (server.lua_time_limit > 0 && server.masterhost == NULL) { // 当 lua 解释器执行了 100000,luaMaskCountHook() 会被调用 lua_sethook(lua,luaMaskCountHook,LUA_MASKCOUNT,100000); delhook = 1; } // 现在,我们确定函数已经注册成功了.可以直接调用 lua 脚本 /* At this point whether this script was never seen before or if it was * already defined, we can call it. We have zero arguments and expect * a single return value. */ err = lua_pcall(lua,0,1,-2); // 删除超时回调函数 /* Perform some cleanup that we need to do both on error and success. */ if (delhook) lua_sethook(lua,luaMaskCountHook,0,0); /* Disable hook */ // 如果已经超时了,说明 lua 脚本已在超时后背 SCRPIT KILL 终结了 // 恢复监听发送 lua 脚本命令的客户端 if (server.lua_timedout) { server.lua_timedout = 0; /* Restore the readable handler that was unregistered when the * script timeout was detected. */ aeCreateFileEvent(server.el,c->fd,AE_READABLE, readQueryFromClient,c); } // lua_caller 置空 server.lua_caller = NULL; // 执行 lua 脚本用的是 lua 脚本执行专用的数据集。现在恢复原有的数据集 selectDb(c,server.lua_client->db->id); /* set DB ID from Lua client */ // Garbage collection 垃圾回收 lua_gc(lua,LUA_GCSTEP,1); // 处理执行 lua 脚本的错误 if (err) { // 告知客户端 addReplyErrorFormat(c,"Error running script (call to %s): %s\n", funcname, lua_tostring(lua,-1)); lua_pop(lua,2); /* Consume the Lua reply and remove error handler. */ // 成功了 } else { /* On success convert the Lua return value into Redis protocol, and * send it to * the client. */ luaReplyToRedisReply(c,lua); /* Convert and consume the reply. */ lua_pop(lua,1); /* Remove the error handler. */ } // 将 lua 脚本发布到主从复制上,并写入 AOF 文件 /* EVALSHA should be propagated to Slave and AOF file as full EVAL, unless * we are sure that the script was already in the context of all the * attached slaves *and* the current AOF file if enabled. * * To do so we use a cache of SHA1s of scripts that we already propagated * as full EVAL, that's called the Replication Script Cache. * * For repliation, everytime a new slave attaches to the master, we need to * flush our cache of scripts that can be replicated as EVALSHA, while * for AOF we need to do so every time we rewrite the AOF file. */ if (evalsha) { if (!replicationScriptCacheExists(c->argv[1]->ptr)) { /* This script is not in our script cache, replicate it as * EVAL, then add it into the script cache, as from now on * slaves and AOF know about it. */ // 从 server.lua_scripts 获取 lua 脚本 // c->argv[1]->ptr 是 SHA1 robj *script = dictFetchValue(server.lua_scripts,c->argv[1]->ptr); // 添加到主从复制专用的脚本缓存中 replicationScriptCacheAdd(c->argv[1]->ptr); redisAssertWithInfo(c,NULL,script != NULL); // 重写命令 // 参数 1 为:EVAL // 参数 2 为:lua_script // 如此一来在执行 AOF 持久化和主从复制的时候,lua 脚本就能得到传播 rewriteClientCommandArgument(c,0, resetRefCount(createStringObject("EVAL",4))); rewriteClientCommandArgument(c,1,script); } } }
/* Returns ID(s) of documents added */ void jsondocsetbyjsonCommand(redisClient *c) { /* args 0-N: ["jsondocsetbyjson", id-field-name, json] */ jsonSyncClients(c); sds field_name = c->argv[1]->ptr; struct jsonObj *root = yajl_decode(c->argv[2]->ptr, NULL); if (!root) { addReply(c, g.err_parse); return; } robj *key; if (root->type == JSON_TYPE_LIST) { robj *keys[root->content.obj.elements]; D("Procesing list!\n"); /* First, process all documents for names to make sure they are valid * documents. We don't want to add half the documents then reach a * failure scenario. */ for (int i = 0; i < root->content.obj.elements; i++) { struct jsonObj *o = root->content.obj.fields[i]; key = jsonObjFindId(field_name, o); keys[i] = key; if (!key) { /* Free any allocated keys so far */ for (int j = 0; i < j; j++) decrRefCount(keys[j]); jsonObjFree(root); addReplyErrorFormat( c, "field '%s' not found or unusable as key for document %d", field_name, i); return; } } /* Now actually add all the documents */ /* Note how a multi-set gets a multibulk reply while * a regular one-document set gets just one bulk result. */ addReplyMultiBulkLen(c, root->content.obj.elements); for (int i = 0; i < root->content.obj.elements; i++) { struct jsonObj *o = root->content.obj.fields[i]; jsonObjAddToDB(keys[i], o); addReplyBulkCBuffer(c, keys[i]->ptr, sdslen(keys[i]->ptr)); decrRefCount(keys[i]); } } else if (root->type == JSON_TYPE_MAP) { key = jsonObjFindId(field_name, root); if (key) { jsonObjAddToDB(key, root); addReplyBulkCBuffer(c, key->ptr, sdslen(key->ptr)); decrRefCount(key); } else { addReplyErrorFormat( c, "field '%s' not found or unusable as key for document", field_name); } } else { addReplyError(c, "JSON isn't map or array of maps."); } jsonObjFree(root); }
void jsondocsetCommand(redisClient *c) { /* args 0-N: ["jsondocset", id, json, [id2, json2, ...]] */ /* If only K:V strings, submit as one hmset command */ /* This function is O(3*N) in the number of total keys submitted: * - first, we validate the keys * - second, we process all the json * - third, we add all the JSON to the DB. * + we run each loop indepdently because we must be able to abort * the entire add sequence if any prior aggregate attempt fails * (e.g. if you have a bad key or bad json, we don't want to add * _any_ of these documents.) */ /* If we don't have an even number of Key/Document pairs, exit. */ if ((c->argc - 1) % 2 != 0) { addReplyError( c, "Invalid number of arguments. Must match keys to documents."); return; } /* If any of the keys are invalid, exit. */ for (int i = 1; i < c->argc; i += 2) { if (!validateKeyFormatAndReply(c, c->argv[i]->ptr)) return; } int documents = (c->argc - 1) / 2; struct jsonObj *additions[documents]; for (int i = 1; i < c->argc; i += 2) { struct jsonObj *root = yajl_decode(c->argv[i + 1]->ptr, NULL); additions[i / 2] = root; if (!root) { /* Free any jsonObj trees already created */ for (int j = 0; j < i; j++) jsonObjFree(additions[j]); addReplyErrorFormat(c, "Invalid JSON at document %d. Use " "JSONDOCVALIDATE [json] for complete error.", i / 2); return; } } jsonSyncClients(c); int deleted = 0; /* Now jump keys at positions i*2 and documents at i/2 */ for (int i = 1; i < c->argc; i += 2) { struct jsonObj *root = additions[i / 2]; /* We can add a much simpler "check if key exists" test here first: */ deleted += jsonObjAddToDB(c->argv[i], root); jsonObjFree(root); D("Parsed Json %d!\n", i); } /* Reply += 1 if the document is new; reply += 0 if the document updated * (where an update is a full Delete/Create cycle) */ /* Replies with number of new keys set. Existing keys also get * set, but don't count as new. */ addReplyLongLong(c, documents - deleted); }
void evalGenericCommand(redisClient *c, int evalsha) { lua_State *lua = server.lua; char funcname[43]; long long numkeys; int delhook = 0, err; /* We want the same PRNG sequence at every call so that our PRNG is * not affected by external state. */ redisSrand48(0); /* We set this flag to zero to remember that so far no random command * was called. This way we can allow the user to call commands like * SRANDMEMBER or RANDOMKEY from Lua scripts as far as no write command * is called (otherwise the replication and AOF would end with non * deterministic sequences). * * Thanks to this flag we'll raise an error every time a write command * is called after a random command was used. */ server.lua_random_dirty = 0; server.lua_write_dirty = 0; /* Get the number of arguments that are keys */ if (getLongLongFromObjectOrReply(c,c->argv[2],&numkeys,NULL) != REDIS_OK) return; if (numkeys > (c->argc - 3)) { addReplyError(c,"Number of keys can't be greater than number of args"); return; } /* We obtain the script SHA1, then check if this function is already * defined into the Lua state */ funcname[0] = 'f'; funcname[1] = '_'; if (!evalsha) { /* Hash the code if this is an EVAL call */ sha1hex(funcname+2,c->argv[1]->ptr,sdslen(c->argv[1]->ptr)); } else { /* We already have the SHA if it is a EVALSHA */ int j; char *sha = c->argv[1]->ptr; for (j = 0; j < 40; j++) funcname[j+2] = tolower(sha[j]); funcname[42] = '\0'; } /* Push the pcall error handler function on the stack. */ lua_getglobal(lua, "__redis__err__handler"); /* Try to lookup the Lua function */ lua_getglobal(lua, funcname); if (lua_isnil(lua,-1)) { lua_pop(lua,1); /* remove the nil from the stack */ /* Function not defined... let's define it if we have the * body of the function. If this is an EVALSHA call we can just * return an error. */ if (evalsha) { lua_pop(lua,1); /* remove the error handler from the stack. */ addReply(c, shared.noscripterr); return; } if (luaCreateFunction(c,lua,funcname,c->argv[1]) == REDIS_ERR) { lua_pop(lua,1); /* remove the error handler from the stack. */ /* The error is sent to the client by luaCreateFunction() * itself when it returns REDIS_ERR. */ return; } /* Now the following is guaranteed to return non nil */ lua_getglobal(lua, funcname); redisAssert(!lua_isnil(lua,-1)); } /* Populate the argv and keys table accordingly to the arguments that * EVAL received. */ luaSetGlobalArray(lua,"KEYS",c->argv+3,numkeys); luaSetGlobalArray(lua,"ARGV",c->argv+3+numkeys,c->argc-3-numkeys); /* Select the right DB in the context of the Lua client */ selectDb(server.lua_client,c->db->id); /* Set a hook in order to be able to stop the script execution if it * is running for too much time. * We set the hook only if the time limit is enabled as the hook will * make the Lua script execution slower. */ server.lua_caller = c; server.lua_time_start = mstime(); server.lua_kill = 0; if (server.lua_time_limit > 0 && server.masterhost == NULL) { lua_sethook(lua,luaMaskCountHook,LUA_MASKCOUNT,100000); delhook = 1; } /* At this point whether this script was never seen before or if it was * already defined, we can call it. We have zero arguments and expect * a single return value. */ err = lua_pcall(lua,0,1,-2); /* Perform some cleanup that we need to do both on error and success. */ if (delhook) lua_sethook(lua,luaMaskCountHook,0,0); /* Disable hook */ if (server.lua_timedout) { server.lua_timedout = 0; /* Restore the readable handler that was unregistered when the * script timeout was detected. */ aeCreateFileEvent(server.el,c->fd,AE_READABLE, readQueryFromClient,c); } server.lua_caller = NULL; selectDb(c,server.lua_client->db->id); /* set DB ID from Lua client */ lua_gc(lua,LUA_GCSTEP,1); if (err) { addReplyErrorFormat(c,"Error running script (call to %s): %s\n", funcname, lua_tostring(lua,-1)); lua_pop(lua,2); /* Consume the Lua reply and remove error handler. */ } else { /* On success convert the Lua return value into Redis protocol, and * send it to * the client. */ luaReplyToRedisReply(c,lua); /* Convert and consume the reply. */ lua_pop(lua,1); /* Remove the error handler. */ } /* EVALSHA should be propagated to Slave and AOF file as full EVAL, unless * we are sure that the script was already in the context of all the * attached slaves *and* the current AOF file if enabled. * * To do so we use a cache of SHA1s of scripts that we already propagated * as full EVAL, that's called the Replication Script Cache. * * For repliation, everytime a new slave attaches to the master, we need to * flush our cache of scripts that can be replicated as EVALSHA, while * for AOF we need to do so every time we rewrite the AOF file. */ if (evalsha) { if (!replicationScriptCacheExists(c->argv[1]->ptr)) { /* This script is not in our script cache, replicate it as * EVAL, then add it into the script cache, as from now on * slaves and AOF know about it. */ robj *script = dictFetchValue(server.lua_scripts,c->argv[1]->ptr); replicationScriptCacheAdd(c->argv[1]->ptr); redisAssertWithInfo(c,NULL,script != NULL); rewriteClientCommandArgument(c,0, resetRefCount(createStringObject("EVAL",4))); rewriteClientCommandArgument(c,1,script); forceCommandPropagation(c,REDIS_PROPAGATE_REPL|REDIS_PROPAGATE_AOF); } } }
/* PUBSUB command for Pub/Sub introspection. */ void pubsubCommand(redisClient *c) { // PUBSUB CHANNELS [pattern] 子命令 if (!strcasecmp(c->argv[1]->ptr,"channels") && (c->argc == 2 || c->argc ==3)) { /* PUBSUB CHANNELS [<pattern>] */ // 检查命令请求是否给定了 pattern 参数 // 如果没有给定的话,就设为 NULL sds pat = (c->argc == 2) ? NULL : c->argv[2]->ptr; // 创建 pubsub_channels 的字典迭代器 // 该字典的键为频道,值为链表 // 链表中保存了所有订阅键所对应的频道的客户端 dictIterator *di = dictGetIterator(server.pubsub_channels); dictEntry *de; long mblen = 0; void *replylen; replylen = addDeferredMultiBulkLength(c); // 从迭代器中获取一个客户端 while((de = dictNext(di)) != NULL) { // 从字典中取出客户端所订阅的频道 robj *cobj = dictGetKey(de); sds channel = cobj->ptr; // 顺带一提 // 因为 Redis 的字典实现只能遍历字典的值(客户端) // 所以这里才会有遍历字典值然后通过字典值取出字典键(频道)的蹩脚用法 // 如果没有给定 pattern 参数,那么打印所有找到的频道 // 如果给定了 pattern 参数,那么只打印和 pattern 相匹配的频道 if (!pat || stringmatchlen(pat, sdslen(pat), channel, sdslen(channel),0)) { // 向客户端输出频道 addReplyBulk(c,cobj); mblen++; } } // 释放字典迭代器 dictReleaseIterator(di); setDeferredMultiBulkLength(c,replylen,mblen); // PUBSUB NUMSUB [channel-1 channel-2 ... channel-N] 子命令 } else if (!strcasecmp(c->argv[1]->ptr,"numsub") && c->argc >= 2) { /* PUBSUB NUMSUB [Channel_1 ... Channel_N] */ int j; addReplyMultiBulkLen(c,(c->argc-2)*2); for (j = 2; j < c->argc; j++) { // c->argv[j] 也即是客户端输入的第 N 个频道名字 // pubsub_channels 的字典为频道名字 // 而值则是保存了 c->argv[j] 频道所有订阅者的链表 // 而调用 dictFetchValue 也就是取出所有订阅给定频道的客户端 list *l = dictFetchValue(server.pubsub_channels,c->argv[j]); addReplyBulk(c,c->argv[j]); // 向客户端返回链表的长度属性 // 这个属性就是某个频道的订阅者数量 // 例如:如果一个频道有三个订阅者,那么链表的长度就是 3 // 而返回给客户端的数字也是三 addReplyBulkLongLong(c,l ? listLength(l) : 0); } // PUBSUB NUMPAT 子命令 } else if (!strcasecmp(c->argv[1]->ptr,"numpat") && c->argc == 2) { /* PUBSUB NUMPAT */ // pubsub_patterns 链表保存了服务器中所有被订阅的模式 // pubsub_patterns 的长度就是服务器中被订阅模式的数量 addReplyLongLong(c,listLength(server.pubsub_patterns)); // 错误处理 } else { addReplyErrorFormat(c, "Unknown PUBSUB subcommand or wrong number of arguments for '%s'", (char*)c->argv[1]->ptr); } }