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 <math.h> /* isnan(), isinf() */ |
32 | |
33 | /* Forward declarations */ |
34 | int getGenericCommand(client *c); |
35 | |
36 | /*----------------------------------------------------------------------------- |
37 | * String Commands |
38 | *----------------------------------------------------------------------------*/ |
39 | |
40 | static int checkStringLength(client *c, long long size) { |
41 | if (!mustObeyClient(c) && size > server.proto_max_bulk_len) { |
42 | addReplyError(c,"string exceeds maximum allowed size (proto-max-bulk-len)" ); |
43 | return C_ERR; |
44 | } |
45 | return C_OK; |
46 | } |
47 | |
48 | /* The setGenericCommand() function implements the SET operation with different |
49 | * options and variants. This function is called in order to implement the |
50 | * following commands: SET, SETEX, PSETEX, SETNX, GETSET. |
51 | * |
52 | * 'flags' changes the behavior of the command (NX, XX or GET, see below). |
53 | * |
54 | * 'expire' represents an expire to set in form of a Redis object as passed |
55 | * by the user. It is interpreted according to the specified 'unit'. |
56 | * |
57 | * 'ok_reply' and 'abort_reply' is what the function will reply to the client |
58 | * if the operation is performed, or when it is not because of NX or |
59 | * XX flags. |
60 | * |
61 | * If ok_reply is NULL "+OK" is used. |
62 | * If abort_reply is NULL, "$-1" is used. */ |
63 | |
64 | #define OBJ_NO_FLAGS 0 |
65 | #define OBJ_SET_NX (1<<0) /* Set if key not exists. */ |
66 | #define OBJ_SET_XX (1<<1) /* Set if key exists. */ |
67 | #define OBJ_EX (1<<2) /* Set if time in seconds is given */ |
68 | #define OBJ_PX (1<<3) /* Set if time in ms in given */ |
69 | #define OBJ_KEEPTTL (1<<4) /* Set and keep the ttl */ |
70 | #define OBJ_SET_GET (1<<5) /* Set if want to get key before set */ |
71 | #define OBJ_EXAT (1<<6) /* Set if timestamp in second is given */ |
72 | #define OBJ_PXAT (1<<7) /* Set if timestamp in ms is given */ |
73 | #define OBJ_PERSIST (1<<8) /* Set if we need to remove the ttl */ |
74 | |
75 | /* Forward declaration */ |
76 | static int getExpireMillisecondsOrReply(client *c, robj *expire, int flags, int unit, long long *milliseconds); |
77 | |
78 | void setGenericCommand(client *c, int flags, robj *key, robj *val, robj *expire, int unit, robj *ok_reply, robj *abort_reply) { |
79 | long long milliseconds = 0; /* initialized to avoid any harmness warning */ |
80 | int found = 0; |
81 | int setkey_flags = 0; |
82 | |
83 | if (expire && getExpireMillisecondsOrReply(c, expire, flags, unit, &milliseconds) != C_OK) { |
84 | return; |
85 | } |
86 | |
87 | if (flags & OBJ_SET_GET) { |
88 | if (getGenericCommand(c) == C_ERR) return; |
89 | } |
90 | |
91 | found = (lookupKeyWrite(c->db,key) != NULL); |
92 | |
93 | if ((flags & OBJ_SET_NX && found) || |
94 | (flags & OBJ_SET_XX && !found)) |
95 | { |
96 | if (!(flags & OBJ_SET_GET)) { |
97 | addReply(c, abort_reply ? abort_reply : shared.null[c->resp]); |
98 | } |
99 | return; |
100 | } |
101 | |
102 | setkey_flags |= (flags & OBJ_KEEPTTL) ? SETKEY_KEEPTTL : 0; |
103 | setkey_flags |= found ? SETKEY_ALREADY_EXIST : SETKEY_DOESNT_EXIST; |
104 | |
105 | setKey(c,c->db,key,val,setkey_flags); |
106 | server.dirty++; |
107 | notifyKeyspaceEvent(NOTIFY_STRING,"set" ,key,c->db->id); |
108 | |
109 | if (expire) { |
110 | setExpire(c,c->db,key,milliseconds); |
111 | /* Propagate as SET Key Value PXAT millisecond-timestamp if there is |
112 | * EX/PX/EXAT/PXAT flag. */ |
113 | robj *milliseconds_obj = createStringObjectFromLongLong(milliseconds); |
114 | rewriteClientCommandVector(c, 5, shared.set, key, val, shared.pxat, milliseconds_obj); |
115 | decrRefCount(milliseconds_obj); |
116 | notifyKeyspaceEvent(NOTIFY_GENERIC,"expire" ,key,c->db->id); |
117 | } |
118 | |
119 | if (!(flags & OBJ_SET_GET)) { |
120 | addReply(c, ok_reply ? ok_reply : shared.ok); |
121 | } |
122 | |
123 | /* Propagate without the GET argument (Isn't needed if we had expire since in that case we completely re-written the command argv) */ |
124 | if ((flags & OBJ_SET_GET) && !expire) { |
125 | int argc = 0; |
126 | int j; |
127 | robj **argv = zmalloc((c->argc-1)*sizeof(robj*)); |
128 | for (j=0; j < c->argc; j++) { |
129 | char *a = c->argv[j]->ptr; |
130 | /* Skip GET which may be repeated multiple times. */ |
131 | if (j >= 3 && |
132 | (a[0] == 'g' || a[0] == 'G') && |
133 | (a[1] == 'e' || a[1] == 'E') && |
134 | (a[2] == 't' || a[2] == 'T') && a[3] == '\0') |
135 | continue; |
136 | argv[argc++] = c->argv[j]; |
137 | incrRefCount(c->argv[j]); |
138 | } |
139 | replaceClientCommandVector(c, argc, argv); |
140 | } |
141 | } |
142 | |
143 | /* |
144 | * Extract the `expire` argument of a given GET/SET command as an absolute timestamp in milliseconds. |
145 | * |
146 | * "client" is the client that sent the `expire` argument. |
147 | * "expire" is the `expire` argument to be extracted. |
148 | * "flags" represents the behavior of the command (e.g. PX or EX). |
149 | * "unit" is the original unit of the given `expire` argument (e.g. UNIT_SECONDS). |
150 | * "milliseconds" is output argument. |
151 | * |
152 | * If return C_OK, "milliseconds" output argument will be set to the resulting absolute timestamp. |
153 | * If return C_ERR, an error reply has been added to the given client. |
154 | */ |
155 | static int getExpireMillisecondsOrReply(client *c, robj *expire, int flags, int unit, long long *milliseconds) { |
156 | int ret = getLongLongFromObjectOrReply(c, expire, milliseconds, NULL); |
157 | if (ret != C_OK) { |
158 | return ret; |
159 | } |
160 | |
161 | if (*milliseconds <= 0 || (unit == UNIT_SECONDS && *milliseconds > LLONG_MAX / 1000)) { |
162 | /* Negative value provided or multiplication is gonna overflow. */ |
163 | addReplyErrorExpireTime(c); |
164 | return C_ERR; |
165 | } |
166 | |
167 | if (unit == UNIT_SECONDS) *milliseconds *= 1000; |
168 | |
169 | if ((flags & OBJ_PX) || (flags & OBJ_EX)) { |
170 | *milliseconds += mstime(); |
171 | } |
172 | |
173 | if (*milliseconds <= 0) { |
174 | /* Overflow detected. */ |
175 | addReplyErrorExpireTime(c); |
176 | return C_ERR; |
177 | } |
178 | |
179 | return C_OK; |
180 | } |
181 | |
182 | #define COMMAND_GET 0 |
183 | #define COMMAND_SET 1 |
184 | /* |
185 | * The parseExtendedStringArgumentsOrReply() function performs the common validation for extended |
186 | * string arguments used in SET and GET command. |
187 | * |
188 | * Get specific commands - PERSIST/DEL |
189 | * Set specific commands - XX/NX/GET |
190 | * Common commands - EX/EXAT/PX/PXAT/KEEPTTL |
191 | * |
192 | * Function takes pointers to client, flags, unit, pointer to pointer of expire obj if needed |
193 | * to be determined and command_type which can be COMMAND_GET or COMMAND_SET. |
194 | * |
195 | * If there are any syntax violations C_ERR is returned else C_OK is returned. |
196 | * |
197 | * Input flags are updated upon parsing the arguments. Unit and expire are updated if there are any |
198 | * EX/EXAT/PX/PXAT arguments. Unit is updated to millisecond if PX/PXAT is set. |
199 | */ |
200 | int parseExtendedStringArgumentsOrReply(client *c, int *flags, int *unit, robj **expire, int command_type) { |
201 | |
202 | int j = command_type == COMMAND_GET ? 2 : 3; |
203 | for (; j < c->argc; j++) { |
204 | char *opt = c->argv[j]->ptr; |
205 | robj *next = (j == c->argc-1) ? NULL : c->argv[j+1]; |
206 | |
207 | if ((opt[0] == 'n' || opt[0] == 'N') && |
208 | (opt[1] == 'x' || opt[1] == 'X') && opt[2] == '\0' && |
209 | !(*flags & OBJ_SET_XX) && (command_type == COMMAND_SET)) |
210 | { |
211 | *flags |= OBJ_SET_NX; |
212 | } else if ((opt[0] == 'x' || opt[0] == 'X') && |
213 | (opt[1] == 'x' || opt[1] == 'X') && opt[2] == '\0' && |
214 | !(*flags & OBJ_SET_NX) && (command_type == COMMAND_SET)) |
215 | { |
216 | *flags |= OBJ_SET_XX; |
217 | } else if ((opt[0] == 'g' || opt[0] == 'G') && |
218 | (opt[1] == 'e' || opt[1] == 'E') && |
219 | (opt[2] == 't' || opt[2] == 'T') && opt[3] == '\0' && |
220 | (command_type == COMMAND_SET)) |
221 | { |
222 | *flags |= OBJ_SET_GET; |
223 | } else if (!strcasecmp(opt, "KEEPTTL" ) && !(*flags & OBJ_PERSIST) && |
224 | !(*flags & OBJ_EX) && !(*flags & OBJ_EXAT) && |
225 | !(*flags & OBJ_PX) && !(*flags & OBJ_PXAT) && (command_type == COMMAND_SET)) |
226 | { |
227 | *flags |= OBJ_KEEPTTL; |
228 | } else if (!strcasecmp(opt,"PERSIST" ) && (command_type == COMMAND_GET) && |
229 | !(*flags & OBJ_EX) && !(*flags & OBJ_EXAT) && |
230 | !(*flags & OBJ_PX) && !(*flags & OBJ_PXAT) && |
231 | !(*flags & OBJ_KEEPTTL)) |
232 | { |
233 | *flags |= OBJ_PERSIST; |
234 | } else if ((opt[0] == 'e' || opt[0] == 'E') && |
235 | (opt[1] == 'x' || opt[1] == 'X') && opt[2] == '\0' && |
236 | !(*flags & OBJ_KEEPTTL) && !(*flags & OBJ_PERSIST) && |
237 | !(*flags & OBJ_EXAT) && !(*flags & OBJ_PX) && |
238 | !(*flags & OBJ_PXAT) && next) |
239 | { |
240 | *flags |= OBJ_EX; |
241 | *expire = next; |
242 | j++; |
243 | } else if ((opt[0] == 'p' || opt[0] == 'P') && |
244 | (opt[1] == 'x' || opt[1] == 'X') && opt[2] == '\0' && |
245 | !(*flags & OBJ_KEEPTTL) && !(*flags & OBJ_PERSIST) && |
246 | !(*flags & OBJ_EX) && !(*flags & OBJ_EXAT) && |
247 | !(*flags & OBJ_PXAT) && next) |
248 | { |
249 | *flags |= OBJ_PX; |
250 | *unit = UNIT_MILLISECONDS; |
251 | *expire = next; |
252 | j++; |
253 | } else if ((opt[0] == 'e' || opt[0] == 'E') && |
254 | (opt[1] == 'x' || opt[1] == 'X') && |
255 | (opt[2] == 'a' || opt[2] == 'A') && |
256 | (opt[3] == 't' || opt[3] == 'T') && opt[4] == '\0' && |
257 | !(*flags & OBJ_KEEPTTL) && !(*flags & OBJ_PERSIST) && |
258 | !(*flags & OBJ_EX) && !(*flags & OBJ_PX) && |
259 | !(*flags & OBJ_PXAT) && next) |
260 | { |
261 | *flags |= OBJ_EXAT; |
262 | *expire = next; |
263 | j++; |
264 | } else if ((opt[0] == 'p' || opt[0] == 'P') && |
265 | (opt[1] == 'x' || opt[1] == 'X') && |
266 | (opt[2] == 'a' || opt[2] == 'A') && |
267 | (opt[3] == 't' || opt[3] == 'T') && opt[4] == '\0' && |
268 | !(*flags & OBJ_KEEPTTL) && !(*flags & OBJ_PERSIST) && |
269 | !(*flags & OBJ_EX) && !(*flags & OBJ_EXAT) && |
270 | !(*flags & OBJ_PX) && next) |
271 | { |
272 | *flags |= OBJ_PXAT; |
273 | *unit = UNIT_MILLISECONDS; |
274 | *expire = next; |
275 | j++; |
276 | } else { |
277 | addReplyErrorObject(c,shared.syntaxerr); |
278 | return C_ERR; |
279 | } |
280 | } |
281 | return C_OK; |
282 | } |
283 | |
284 | /* SET key value [NX] [XX] [KEEPTTL] [GET] [EX <seconds>] [PX <milliseconds>] |
285 | * [EXAT <seconds-timestamp>][PXAT <milliseconds-timestamp>] */ |
286 | void setCommand(client *c) { |
287 | robj *expire = NULL; |
288 | int unit = UNIT_SECONDS; |
289 | int flags = OBJ_NO_FLAGS; |
290 | |
291 | if (parseExtendedStringArgumentsOrReply(c,&flags,&unit,&expire,COMMAND_SET) != C_OK) { |
292 | return; |
293 | } |
294 | |
295 | c->argv[2] = tryObjectEncoding(c->argv[2]); |
296 | setGenericCommand(c,flags,c->argv[1],c->argv[2],expire,unit,NULL,NULL); |
297 | } |
298 | |
299 | void setnxCommand(client *c) { |
300 | c->argv[2] = tryObjectEncoding(c->argv[2]); |
301 | setGenericCommand(c,OBJ_SET_NX,c->argv[1],c->argv[2],NULL,0,shared.cone,shared.czero); |
302 | } |
303 | |
304 | void setexCommand(client *c) { |
305 | c->argv[3] = tryObjectEncoding(c->argv[3]); |
306 | setGenericCommand(c,OBJ_EX,c->argv[1],c->argv[3],c->argv[2],UNIT_SECONDS,NULL,NULL); |
307 | } |
308 | |
309 | void psetexCommand(client *c) { |
310 | c->argv[3] = tryObjectEncoding(c->argv[3]); |
311 | setGenericCommand(c,OBJ_PX,c->argv[1],c->argv[3],c->argv[2],UNIT_MILLISECONDS,NULL,NULL); |
312 | } |
313 | |
314 | int getGenericCommand(client *c) { |
315 | robj *o; |
316 | |
317 | if ((o = lookupKeyReadOrReply(c,c->argv[1],shared.null[c->resp])) == NULL) |
318 | return C_OK; |
319 | |
320 | if (checkType(c,o,OBJ_STRING)) { |
321 | return C_ERR; |
322 | } |
323 | |
324 | addReplyBulk(c,o); |
325 | return C_OK; |
326 | } |
327 | |
328 | void getCommand(client *c) { |
329 | getGenericCommand(c); |
330 | } |
331 | |
332 | /* |
333 | * GETEX <key> [PERSIST][EX seconds][PX milliseconds][EXAT seconds-timestamp][PXAT milliseconds-timestamp] |
334 | * |
335 | * The getexCommand() function implements extended options and variants of the GET command. Unlike GET |
336 | * command this command is not read-only. |
337 | * |
338 | * The default behavior when no options are specified is same as GET and does not alter any TTL. |
339 | * |
340 | * Only one of the below options can be used at a given time. |
341 | * |
342 | * 1. PERSIST removes any TTL associated with the key. |
343 | * 2. EX Set expiry TTL in seconds. |
344 | * 3. PX Set expiry TTL in milliseconds. |
345 | * 4. EXAT Same like EX instead of specifying the number of seconds representing the TTL |
346 | * (time to live), it takes an absolute Unix timestamp |
347 | * 5. PXAT Same like PX instead of specifying the number of milliseconds representing the TTL |
348 | * (time to live), it takes an absolute Unix timestamp |
349 | * |
350 | * Command would either return the bulk string, error or nil. |
351 | */ |
352 | void getexCommand(client *c) { |
353 | robj *expire = NULL; |
354 | int unit = UNIT_SECONDS; |
355 | int flags = OBJ_NO_FLAGS; |
356 | |
357 | if (parseExtendedStringArgumentsOrReply(c,&flags,&unit,&expire,COMMAND_GET) != C_OK) { |
358 | return; |
359 | } |
360 | |
361 | robj *o; |
362 | |
363 | if ((o = lookupKeyReadOrReply(c,c->argv[1],shared.null[c->resp])) == NULL) |
364 | return; |
365 | |
366 | if (checkType(c,o,OBJ_STRING)) { |
367 | return; |
368 | } |
369 | |
370 | /* Validate the expiration time value first */ |
371 | long long milliseconds = 0; |
372 | if (expire && getExpireMillisecondsOrReply(c, expire, flags, unit, &milliseconds) != C_OK) { |
373 | return; |
374 | } |
375 | |
376 | /* We need to do this before we expire the key or delete it */ |
377 | addReplyBulk(c,o); |
378 | |
379 | /* This command is never propagated as is. It is either propagated as PEXPIRE[AT],DEL,UNLINK or PERSIST. |
380 | * This why it doesn't need special handling in feedAppendOnlyFile to convert relative expire time to absolute one. */ |
381 | if (((flags & OBJ_PXAT) || (flags & OBJ_EXAT)) && checkAlreadyExpired(milliseconds)) { |
382 | /* When PXAT/EXAT absolute timestamp is specified, there can be a chance that timestamp |
383 | * has already elapsed so delete the key in that case. */ |
384 | int deleted = server.lazyfree_lazy_expire ? dbAsyncDelete(c->db, c->argv[1]) : |
385 | dbSyncDelete(c->db, c->argv[1]); |
386 | serverAssert(deleted); |
387 | robj *aux = server.lazyfree_lazy_expire ? shared.unlink : shared.del; |
388 | rewriteClientCommandVector(c,2,aux,c->argv[1]); |
389 | signalModifiedKey(c, c->db, c->argv[1]); |
390 | notifyKeyspaceEvent(NOTIFY_GENERIC, "del" , c->argv[1], c->db->id); |
391 | server.dirty++; |
392 | } else if (expire) { |
393 | setExpire(c,c->db,c->argv[1],milliseconds); |
394 | /* Propagate as PXEXPIREAT millisecond-timestamp if there is |
395 | * EX/PX/EXAT/PXAT flag and the key has not expired. */ |
396 | robj *milliseconds_obj = createStringObjectFromLongLong(milliseconds); |
397 | rewriteClientCommandVector(c,3,shared.pexpireat,c->argv[1],milliseconds_obj); |
398 | decrRefCount(milliseconds_obj); |
399 | signalModifiedKey(c, c->db, c->argv[1]); |
400 | notifyKeyspaceEvent(NOTIFY_GENERIC,"expire" ,c->argv[1],c->db->id); |
401 | server.dirty++; |
402 | } else if (flags & OBJ_PERSIST) { |
403 | if (removeExpire(c->db, c->argv[1])) { |
404 | signalModifiedKey(c, c->db, c->argv[1]); |
405 | rewriteClientCommandVector(c, 2, shared.persist, c->argv[1]); |
406 | notifyKeyspaceEvent(NOTIFY_GENERIC,"persist" ,c->argv[1],c->db->id); |
407 | server.dirty++; |
408 | } |
409 | } |
410 | } |
411 | |
412 | void getdelCommand(client *c) { |
413 | if (getGenericCommand(c) == C_ERR) return; |
414 | int deleted = server.lazyfree_lazy_user_del ? dbAsyncDelete(c->db, c->argv[1]) : |
415 | dbSyncDelete(c->db, c->argv[1]); |
416 | if (deleted) { |
417 | /* Propagate as DEL/UNLINK command */ |
418 | robj *aux = server.lazyfree_lazy_user_del ? shared.unlink : shared.del; |
419 | rewriteClientCommandVector(c,2,aux,c->argv[1]); |
420 | signalModifiedKey(c, c->db, c->argv[1]); |
421 | notifyKeyspaceEvent(NOTIFY_GENERIC, "del" , c->argv[1], c->db->id); |
422 | server.dirty++; |
423 | } |
424 | } |
425 | |
426 | void getsetCommand(client *c) { |
427 | if (getGenericCommand(c) == C_ERR) return; |
428 | c->argv[2] = tryObjectEncoding(c->argv[2]); |
429 | setKey(c,c->db,c->argv[1],c->argv[2],0); |
430 | notifyKeyspaceEvent(NOTIFY_STRING,"set" ,c->argv[1],c->db->id); |
431 | server.dirty++; |
432 | |
433 | /* Propagate as SET command */ |
434 | rewriteClientCommandArgument(c,0,shared.set); |
435 | } |
436 | |
437 | void setrangeCommand(client *c) { |
438 | robj *o; |
439 | long offset; |
440 | sds value = c->argv[3]->ptr; |
441 | |
442 | if (getLongFromObjectOrReply(c,c->argv[2],&offset,NULL) != C_OK) |
443 | return; |
444 | |
445 | if (offset < 0) { |
446 | addReplyError(c,"offset is out of range" ); |
447 | return; |
448 | } |
449 | |
450 | o = lookupKeyWrite(c->db,c->argv[1]); |
451 | if (o == NULL) { |
452 | /* Return 0 when setting nothing on a non-existing string */ |
453 | if (sdslen(value) == 0) { |
454 | addReply(c,shared.czero); |
455 | return; |
456 | } |
457 | |
458 | /* Return when the resulting string exceeds allowed size */ |
459 | if (checkStringLength(c,offset+sdslen(value)) != C_OK) |
460 | return; |
461 | |
462 | o = createObject(OBJ_STRING,sdsnewlen(NULL, offset+sdslen(value))); |
463 | dbAdd(c->db,c->argv[1],o); |
464 | } else { |
465 | size_t olen; |
466 | |
467 | /* Key exists, check type */ |
468 | if (checkType(c,o,OBJ_STRING)) |
469 | return; |
470 | |
471 | /* Return existing string length when setting nothing */ |
472 | olen = stringObjectLen(o); |
473 | if (sdslen(value) == 0) { |
474 | addReplyLongLong(c,olen); |
475 | return; |
476 | } |
477 | |
478 | /* Return when the resulting string exceeds allowed size */ |
479 | if (checkStringLength(c,offset+sdslen(value)) != C_OK) |
480 | return; |
481 | |
482 | /* Create a copy when the object is shared or encoded. */ |
483 | o = dbUnshareStringValue(c->db,c->argv[1],o); |
484 | } |
485 | |
486 | if (sdslen(value) > 0) { |
487 | o->ptr = sdsgrowzero(o->ptr,offset+sdslen(value)); |
488 | memcpy((char*)o->ptr+offset,value,sdslen(value)); |
489 | signalModifiedKey(c,c->db,c->argv[1]); |
490 | notifyKeyspaceEvent(NOTIFY_STRING, |
491 | "setrange" ,c->argv[1],c->db->id); |
492 | server.dirty++; |
493 | } |
494 | addReplyLongLong(c,sdslen(o->ptr)); |
495 | } |
496 | |
497 | void getrangeCommand(client *c) { |
498 | robj *o; |
499 | long long start, end; |
500 | char *str, llbuf[32]; |
501 | size_t strlen; |
502 | |
503 | if (getLongLongFromObjectOrReply(c,c->argv[2],&start,NULL) != C_OK) |
504 | return; |
505 | if (getLongLongFromObjectOrReply(c,c->argv[3],&end,NULL) != C_OK) |
506 | return; |
507 | if ((o = lookupKeyReadOrReply(c,c->argv[1],shared.emptybulk)) == NULL || |
508 | checkType(c,o,OBJ_STRING)) return; |
509 | |
510 | if (o->encoding == OBJ_ENCODING_INT) { |
511 | str = llbuf; |
512 | strlen = ll2string(llbuf,sizeof(llbuf),(long)o->ptr); |
513 | } else { |
514 | str = o->ptr; |
515 | strlen = sdslen(str); |
516 | } |
517 | |
518 | /* Convert negative indexes */ |
519 | if (start < 0 && end < 0 && start > end) { |
520 | addReply(c,shared.emptybulk); |
521 | return; |
522 | } |
523 | if (start < 0) start = strlen+start; |
524 | if (end < 0) end = strlen+end; |
525 | if (start < 0) start = 0; |
526 | if (end < 0) end = 0; |
527 | if ((unsigned long long)end >= strlen) end = strlen-1; |
528 | |
529 | /* Precondition: end >= 0 && end < strlen, so the only condition where |
530 | * nothing can be returned is: start > end. */ |
531 | if (start > end || strlen == 0) { |
532 | addReply(c,shared.emptybulk); |
533 | } else { |
534 | addReplyBulkCBuffer(c,(char*)str+start,end-start+1); |
535 | } |
536 | } |
537 | |
538 | void mgetCommand(client *c) { |
539 | int j; |
540 | |
541 | addReplyArrayLen(c,c->argc-1); |
542 | for (j = 1; j < c->argc; j++) { |
543 | robj *o = lookupKeyRead(c->db,c->argv[j]); |
544 | if (o == NULL) { |
545 | addReplyNull(c); |
546 | } else { |
547 | if (o->type != OBJ_STRING) { |
548 | addReplyNull(c); |
549 | } else { |
550 | addReplyBulk(c,o); |
551 | } |
552 | } |
553 | } |
554 | } |
555 | |
556 | void msetGenericCommand(client *c, int nx) { |
557 | int j; |
558 | |
559 | if ((c->argc % 2) == 0) { |
560 | addReplyErrorArity(c); |
561 | return; |
562 | } |
563 | |
564 | /* Handle the NX flag. The MSETNX semantic is to return zero and don't |
565 | * set anything if at least one key already exists. */ |
566 | if (nx) { |
567 | for (j = 1; j < c->argc; j += 2) { |
568 | if (lookupKeyWrite(c->db,c->argv[j]) != NULL) { |
569 | addReply(c, shared.czero); |
570 | return; |
571 | } |
572 | } |
573 | } |
574 | |
575 | for (j = 1; j < c->argc; j += 2) { |
576 | c->argv[j+1] = tryObjectEncoding(c->argv[j+1]); |
577 | setKey(c,c->db,c->argv[j],c->argv[j+1],0); |
578 | notifyKeyspaceEvent(NOTIFY_STRING,"set" ,c->argv[j],c->db->id); |
579 | } |
580 | server.dirty += (c->argc-1)/2; |
581 | addReply(c, nx ? shared.cone : shared.ok); |
582 | } |
583 | |
584 | void msetCommand(client *c) { |
585 | msetGenericCommand(c,0); |
586 | } |
587 | |
588 | void msetnxCommand(client *c) { |
589 | msetGenericCommand(c,1); |
590 | } |
591 | |
592 | void incrDecrCommand(client *c, long long incr) { |
593 | long long value, oldvalue; |
594 | robj *o, *new; |
595 | |
596 | o = lookupKeyWrite(c->db,c->argv[1]); |
597 | if (checkType(c,o,OBJ_STRING)) return; |
598 | if (getLongLongFromObjectOrReply(c,o,&value,NULL) != C_OK) return; |
599 | |
600 | oldvalue = value; |
601 | if ((incr < 0 && oldvalue < 0 && incr < (LLONG_MIN-oldvalue)) || |
602 | (incr > 0 && oldvalue > 0 && incr > (LLONG_MAX-oldvalue))) { |
603 | addReplyError(c,"increment or decrement would overflow" ); |
604 | return; |
605 | } |
606 | value += incr; |
607 | |
608 | if (o && o->refcount == 1 && o->encoding == OBJ_ENCODING_INT && |
609 | (value < 0 || value >= OBJ_SHARED_INTEGERS) && |
610 | value >= LONG_MIN && value <= LONG_MAX) |
611 | { |
612 | new = o; |
613 | o->ptr = (void*)((long)value); |
614 | } else { |
615 | new = createStringObjectFromLongLongForValue(value); |
616 | if (o) { |
617 | dbOverwrite(c->db,c->argv[1],new); |
618 | } else { |
619 | dbAdd(c->db,c->argv[1],new); |
620 | } |
621 | } |
622 | signalModifiedKey(c,c->db,c->argv[1]); |
623 | notifyKeyspaceEvent(NOTIFY_STRING,"incrby" ,c->argv[1],c->db->id); |
624 | server.dirty++; |
625 | addReplyLongLong(c, value); |
626 | } |
627 | |
628 | void incrCommand(client *c) { |
629 | incrDecrCommand(c,1); |
630 | } |
631 | |
632 | void decrCommand(client *c) { |
633 | incrDecrCommand(c,-1); |
634 | } |
635 | |
636 | void incrbyCommand(client *c) { |
637 | long long incr; |
638 | |
639 | if (getLongLongFromObjectOrReply(c, c->argv[2], &incr, NULL) != C_OK) return; |
640 | incrDecrCommand(c,incr); |
641 | } |
642 | |
643 | void decrbyCommand(client *c) { |
644 | long long incr; |
645 | |
646 | if (getLongLongFromObjectOrReply(c, c->argv[2], &incr, NULL) != C_OK) return; |
647 | /* Overflow check: negating LLONG_MIN will cause an overflow */ |
648 | if (incr == LLONG_MIN) { |
649 | addReplyError(c, "decrement would overflow" ); |
650 | return; |
651 | } |
652 | incrDecrCommand(c,-incr); |
653 | } |
654 | |
655 | void incrbyfloatCommand(client *c) { |
656 | long double incr, value; |
657 | robj *o, *new; |
658 | |
659 | o = lookupKeyWrite(c->db,c->argv[1]); |
660 | if (checkType(c,o,OBJ_STRING)) return; |
661 | if (getLongDoubleFromObjectOrReply(c,o,&value,NULL) != C_OK || |
662 | getLongDoubleFromObjectOrReply(c,c->argv[2],&incr,NULL) != C_OK) |
663 | return; |
664 | |
665 | value += incr; |
666 | if (isnan(value) || isinf(value)) { |
667 | addReplyError(c,"increment would produce NaN or Infinity" ); |
668 | return; |
669 | } |
670 | new = createStringObjectFromLongDouble(value,1); |
671 | if (o) |
672 | dbOverwrite(c->db,c->argv[1],new); |
673 | else |
674 | dbAdd(c->db,c->argv[1],new); |
675 | signalModifiedKey(c,c->db,c->argv[1]); |
676 | notifyKeyspaceEvent(NOTIFY_STRING,"incrbyfloat" ,c->argv[1],c->db->id); |
677 | server.dirty++; |
678 | addReplyBulk(c,new); |
679 | |
680 | /* Always replicate INCRBYFLOAT as a SET command with the final value |
681 | * in order to make sure that differences in float precision or formatting |
682 | * will not create differences in replicas or after an AOF restart. */ |
683 | rewriteClientCommandArgument(c,0,shared.set); |
684 | rewriteClientCommandArgument(c,2,new); |
685 | rewriteClientCommandArgument(c,3,shared.keepttl); |
686 | } |
687 | |
688 | void appendCommand(client *c) { |
689 | size_t totlen; |
690 | robj *o, *append; |
691 | |
692 | o = lookupKeyWrite(c->db,c->argv[1]); |
693 | if (o == NULL) { |
694 | /* Create the key */ |
695 | c->argv[2] = tryObjectEncoding(c->argv[2]); |
696 | dbAdd(c->db,c->argv[1],c->argv[2]); |
697 | incrRefCount(c->argv[2]); |
698 | totlen = stringObjectLen(c->argv[2]); |
699 | } else { |
700 | /* Key exists, check type */ |
701 | if (checkType(c,o,OBJ_STRING)) |
702 | return; |
703 | |
704 | /* "append" is an argument, so always an sds */ |
705 | append = c->argv[2]; |
706 | totlen = stringObjectLen(o)+sdslen(append->ptr); |
707 | if (checkStringLength(c,totlen) != C_OK) |
708 | return; |
709 | |
710 | /* Append the value */ |
711 | o = dbUnshareStringValue(c->db,c->argv[1],o); |
712 | o->ptr = sdscatlen(o->ptr,append->ptr,sdslen(append->ptr)); |
713 | totlen = sdslen(o->ptr); |
714 | } |
715 | signalModifiedKey(c,c->db,c->argv[1]); |
716 | notifyKeyspaceEvent(NOTIFY_STRING,"append" ,c->argv[1],c->db->id); |
717 | server.dirty++; |
718 | addReplyLongLong(c,totlen); |
719 | } |
720 | |
721 | void strlenCommand(client *c) { |
722 | robj *o; |
723 | if ((o = lookupKeyReadOrReply(c,c->argv[1],shared.czero)) == NULL || |
724 | checkType(c,o,OBJ_STRING)) return; |
725 | addReplyLongLong(c,stringObjectLen(o)); |
726 | } |
727 | |
728 | /* LCS key1 key2 [LEN] [IDX] [MINMATCHLEN <len>] [WITHMATCHLEN] */ |
729 | void lcsCommand(client *c) { |
730 | uint32_t i, j; |
731 | long long minmatchlen = 0; |
732 | sds a = NULL, b = NULL; |
733 | int getlen = 0, getidx = 0, withmatchlen = 0; |
734 | robj *obja = NULL, *objb = NULL; |
735 | |
736 | obja = lookupKeyRead(c->db,c->argv[1]); |
737 | objb = lookupKeyRead(c->db,c->argv[2]); |
738 | if ((obja && obja->type != OBJ_STRING) || |
739 | (objb && objb->type != OBJ_STRING)) |
740 | { |
741 | addReplyError(c, |
742 | "The specified keys must contain string values" ); |
743 | /* Don't cleanup the objects, we need to do that |
744 | * only after calling getDecodedObject(). */ |
745 | obja = NULL; |
746 | objb = NULL; |
747 | goto cleanup; |
748 | } |
749 | obja = obja ? getDecodedObject(obja) : createStringObject("" ,0); |
750 | objb = objb ? getDecodedObject(objb) : createStringObject("" ,0); |
751 | a = obja->ptr; |
752 | b = objb->ptr; |
753 | |
754 | for (j = 3; j < (uint32_t)c->argc; j++) { |
755 | char *opt = c->argv[j]->ptr; |
756 | int moreargs = (c->argc-1) - j; |
757 | |
758 | if (!strcasecmp(opt,"IDX" )) { |
759 | getidx = 1; |
760 | } else if (!strcasecmp(opt,"LEN" )) { |
761 | getlen = 1; |
762 | } else if (!strcasecmp(opt,"WITHMATCHLEN" )) { |
763 | withmatchlen = 1; |
764 | } else if (!strcasecmp(opt,"MINMATCHLEN" ) && moreargs) { |
765 | if (getLongLongFromObjectOrReply(c,c->argv[j+1],&minmatchlen,NULL) |
766 | != C_OK) goto cleanup; |
767 | if (minmatchlen < 0) minmatchlen = 0; |
768 | j++; |
769 | } else { |
770 | addReplyErrorObject(c,shared.syntaxerr); |
771 | goto cleanup; |
772 | } |
773 | } |
774 | |
775 | /* Complain if the user passed ambiguous parameters. */ |
776 | if (getlen && getidx) { |
777 | addReplyError(c, |
778 | "If you want both the length and indexes, please just use IDX." ); |
779 | goto cleanup; |
780 | } |
781 | |
782 | /* Detect string truncation or later overflows. */ |
783 | if (sdslen(a) >= UINT32_MAX-1 || sdslen(b) >= UINT32_MAX-1) { |
784 | addReplyError(c, "String too long for LCS" ); |
785 | goto cleanup; |
786 | } |
787 | |
788 | /* Compute the LCS using the vanilla dynamic programming technique of |
789 | * building a table of LCS(x,y) substrings. */ |
790 | uint32_t alen = sdslen(a); |
791 | uint32_t blen = sdslen(b); |
792 | |
793 | /* Setup an uint32_t array to store at LCS[i,j] the length of the |
794 | * LCS A0..i-1, B0..j-1. Note that we have a linear array here, so |
795 | * we index it as LCS[j+(blen+1)*i] */ |
796 | #define LCS(A,B) lcs[(B)+((A)*(blen+1))] |
797 | |
798 | /* Try to allocate the LCS table, and abort on overflow or insufficient memory. */ |
799 | unsigned long long lcssize = (unsigned long long)(alen+1)*(blen+1); /* Can't overflow due to the size limits above. */ |
800 | unsigned long long lcsalloc = lcssize * sizeof(uint32_t); |
801 | uint32_t *lcs = NULL; |
802 | if (lcsalloc < SIZE_MAX && lcsalloc / lcssize == sizeof(uint32_t)) { |
803 | if (lcsalloc > (size_t)server.proto_max_bulk_len) { |
804 | addReplyError(c, "Insufficient memory, transient memory for LCS exceeds proto-max-bulk-len" ); |
805 | goto cleanup; |
806 | } |
807 | lcs = ztrymalloc(lcsalloc); |
808 | } |
809 | if (!lcs) { |
810 | addReplyError(c, "Insufficient memory, failed allocating transient memory for LCS" ); |
811 | goto cleanup; |
812 | } |
813 | |
814 | /* Start building the LCS table. */ |
815 | for (uint32_t i = 0; i <= alen; i++) { |
816 | for (uint32_t j = 0; j <= blen; j++) { |
817 | if (i == 0 || j == 0) { |
818 | /* If one substring has length of zero, the |
819 | * LCS length is zero. */ |
820 | LCS(i,j) = 0; |
821 | } else if (a[i-1] == b[j-1]) { |
822 | /* The len LCS (and the LCS itself) of two |
823 | * sequences with the same final character, is the |
824 | * LCS of the two sequences without the last char |
825 | * plus that last char. */ |
826 | LCS(i,j) = LCS(i-1,j-1)+1; |
827 | } else { |
828 | /* If the last character is different, take the longest |
829 | * between the LCS of the first string and the second |
830 | * minus the last char, and the reverse. */ |
831 | uint32_t lcs1 = LCS(i-1,j); |
832 | uint32_t lcs2 = LCS(i,j-1); |
833 | LCS(i,j) = lcs1 > lcs2 ? lcs1 : lcs2; |
834 | } |
835 | } |
836 | } |
837 | |
838 | /* Store the actual LCS string in "result" if needed. We create |
839 | * it backward, but the length is already known, we store it into idx. */ |
840 | uint32_t idx = LCS(alen,blen); |
841 | sds result = NULL; /* Resulting LCS string. */ |
842 | void *arraylenptr = NULL; /* Deferred length of the array for IDX. */ |
843 | uint32_t arange_start = alen, /* alen signals that values are not set. */ |
844 | arange_end = 0, |
845 | brange_start = 0, |
846 | brange_end = 0; |
847 | |
848 | /* Do we need to compute the actual LCS string? Allocate it in that case. */ |
849 | int computelcs = getidx || !getlen; |
850 | if (computelcs) result = sdsnewlen(SDS_NOINIT,idx); |
851 | |
852 | /* Start with a deferred array if we have to emit the ranges. */ |
853 | uint32_t arraylen = 0; /* Number of ranges emitted in the array. */ |
854 | if (getidx) { |
855 | addReplyMapLen(c,2); |
856 | addReplyBulkCString(c,"matches" ); |
857 | arraylenptr = addReplyDeferredLen(c); |
858 | } |
859 | |
860 | i = alen, j = blen; |
861 | while (computelcs && i > 0 && j > 0) { |
862 | int emit_range = 0; |
863 | if (a[i-1] == b[j-1]) { |
864 | /* If there is a match, store the character and reduce |
865 | * the indexes to look for a new match. */ |
866 | result[idx-1] = a[i-1]; |
867 | |
868 | /* Track the current range. */ |
869 | if (arange_start == alen) { |
870 | arange_start = i-1; |
871 | arange_end = i-1; |
872 | brange_start = j-1; |
873 | brange_end = j-1; |
874 | } else { |
875 | /* Let's see if we can extend the range backward since |
876 | * it is contiguous. */ |
877 | if (arange_start == i && brange_start == j) { |
878 | arange_start--; |
879 | brange_start--; |
880 | } else { |
881 | emit_range = 1; |
882 | } |
883 | } |
884 | /* Emit the range if we matched with the first byte of |
885 | * one of the two strings. We'll exit the loop ASAP. */ |
886 | if (arange_start == 0 || brange_start == 0) emit_range = 1; |
887 | idx--; i--; j--; |
888 | } else { |
889 | /* Otherwise reduce i and j depending on the largest |
890 | * LCS between, to understand what direction we need to go. */ |
891 | uint32_t lcs1 = LCS(i-1,j); |
892 | uint32_t lcs2 = LCS(i,j-1); |
893 | if (lcs1 > lcs2) |
894 | i--; |
895 | else |
896 | j--; |
897 | if (arange_start != alen) emit_range = 1; |
898 | } |
899 | |
900 | /* Emit the current range if needed. */ |
901 | uint32_t match_len = arange_end - arange_start + 1; |
902 | if (emit_range) { |
903 | if (minmatchlen == 0 || match_len >= minmatchlen) { |
904 | if (arraylenptr) { |
905 | addReplyArrayLen(c,2+withmatchlen); |
906 | addReplyArrayLen(c,2); |
907 | addReplyLongLong(c,arange_start); |
908 | addReplyLongLong(c,arange_end); |
909 | addReplyArrayLen(c,2); |
910 | addReplyLongLong(c,brange_start); |
911 | addReplyLongLong(c,brange_end); |
912 | if (withmatchlen) addReplyLongLong(c,match_len); |
913 | arraylen++; |
914 | } |
915 | } |
916 | arange_start = alen; /* Restart at the next match. */ |
917 | } |
918 | } |
919 | |
920 | /* Signal modified key, increment dirty, ... */ |
921 | |
922 | /* Reply depending on the given options. */ |
923 | if (arraylenptr) { |
924 | addReplyBulkCString(c,"len" ); |
925 | addReplyLongLong(c,LCS(alen,blen)); |
926 | setDeferredArrayLen(c,arraylenptr,arraylen); |
927 | } else if (getlen) { |
928 | addReplyLongLong(c,LCS(alen,blen)); |
929 | } else { |
930 | addReplyBulkSds(c,result); |
931 | result = NULL; |
932 | } |
933 | |
934 | /* Cleanup. */ |
935 | sdsfree(result); |
936 | zfree(lcs); |
937 | |
938 | cleanup: |
939 | if (obja) decrRefCount(obja); |
940 | if (objb) decrRefCount(objb); |
941 | return; |
942 | } |
943 | |
944 | |