]>
Commit | Line | Data |
---|---|---|
4365e5b2 | 1 | /* |
2 | * Copyright (c) 2009-2012, Pieter Noordhuis <pcnoordhuis at gmail dot com> | |
3 | * Copyright (c) 2009-2012, Salvatore Sanfilippo <antirez at gmail dot com> | |
4 | * All rights reserved. | |
5 | * | |
6 | * Redistribution and use in source and binary forms, with or without | |
7 | * modification, are permitted provided that the following conditions are met: | |
8 | * | |
9 | * * Redistributions of source code must retain the above copyright notice, | |
10 | * this list of conditions and the following disclaimer. | |
11 | * * Redistributions in binary form must reproduce the above copyright | |
12 | * notice, this list of conditions and the following disclaimer in the | |
13 | * documentation and/or other materials provided with the distribution. | |
14 | * * Neither the name of Redis nor the names of its contributors may be used | |
15 | * to endorse or promote products derived from this software without | |
16 | * specific prior written permission. | |
17 | * | |
18 | * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" | |
19 | * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE | |
20 | * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE | |
21 | * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE | |
22 | * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR | |
23 | * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF | |
24 | * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS | |
25 | * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN | |
26 | * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) | |
27 | * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE | |
28 | * POSSIBILITY OF SUCH DAMAGE. | |
29 | */ | |
30 | ||
31 | ||
08af4d5c PN |
32 | #include <stdlib.h> |
33 | #include <stdio.h> | |
34 | #include <unistd.h> | |
35 | #include <fcntl.h> | |
36 | #include <sys/stat.h> | |
37 | #include <sys/mman.h> | |
38 | #include <string.h> | |
39 | #include <arpa/inet.h> | |
40 | #include <stdint.h> | |
41 | #include <limits.h> | |
42 | #include "lzf.h" | |
43 | ||
44 | /* Object types */ | |
45 | #define REDIS_STRING 0 | |
46 | #define REDIS_LIST 1 | |
47 | #define REDIS_SET 2 | |
48 | #define REDIS_ZSET 3 | |
49 | #define REDIS_HASH 4 | |
2e63cfe2 | 50 | #define REDIS_HASH_ZIPMAP 9 |
51 | #define REDIS_LIST_ZIPLIST 10 | |
52 | #define REDIS_SET_INTSET 11 | |
53 | #define REDIS_ZSET_ZIPLIST 12 | |
f12d0224 | 54 | #define REDIS_HASH_ZIPLIST 13 |
08af4d5c PN |
55 | |
56 | /* Objects encoding. Some kind of objects like Strings and Hashes can be | |
57 | * internally represented in multiple ways. The 'encoding' field of the object | |
58 | * is set to one of this fields for this object. */ | |
59 | #define REDIS_ENCODING_RAW 0 /* Raw representation */ | |
60 | #define REDIS_ENCODING_INT 1 /* Encoded as integer */ | |
61 | #define REDIS_ENCODING_ZIPMAP 2 /* Encoded as zipmap */ | |
62 | #define REDIS_ENCODING_HT 3 /* Encoded as an hash table */ | |
63 | ||
64 | /* Object types only used for dumping to disk */ | |
e5f794ff | 65 | #define REDIS_EXPIRETIME_MS 252 |
08af4d5c PN |
66 | #define REDIS_EXPIRETIME 253 |
67 | #define REDIS_SELECTDB 254 | |
68 | #define REDIS_EOF 255 | |
69 | ||
70 | /* Defines related to the dump file format. To store 32 bits lengths for short | |
71 | * keys requires a lot of space, so we check the most significant 2 bits of | |
72 | * the first byte to interpreter the length: | |
73 | * | |
74 | * 00|000000 => if the two MSB are 00 the len is the 6 bits of this byte | |
75 | * 01|000000 00000000 => 01, the len is 14 byes, 6 bits + 8 bits of next byte | |
76 | * 10|000000 [32 bit integer] => if it's 01, a full 32 bit len will follow | |
77 | * 11|000000 this means: specially encoded object will follow. The six bits | |
78 | * number specify the kind of object that follows. | |
79 | * See the REDIS_RDB_ENC_* defines. | |
80 | * | |
81 | * Lenghts up to 63 are stored using a single byte, most DB keys, and may | |
82 | * values, will fit inside. */ | |
83 | #define REDIS_RDB_6BITLEN 0 | |
84 | #define REDIS_RDB_14BITLEN 1 | |
85 | #define REDIS_RDB_32BITLEN 2 | |
86 | #define REDIS_RDB_ENCVAL 3 | |
87 | #define REDIS_RDB_LENERR UINT_MAX | |
88 | ||
89 | /* When a length of a string object stored on disk has the first two bits | |
90 | * set, the remaining two bits specify a special encoding for the object | |
91 | * accordingly to the following defines: */ | |
92 | #define REDIS_RDB_ENC_INT8 0 /* 8 bit signed integer */ | |
93 | #define REDIS_RDB_ENC_INT16 1 /* 16 bit signed integer */ | |
94 | #define REDIS_RDB_ENC_INT32 2 /* 32 bit signed integer */ | |
95 | #define REDIS_RDB_ENC_LZF 3 /* string compressed with FASTLZ */ | |
96 | ||
97 | #define ERROR(...) { \ | |
98 | printf(__VA_ARGS__); \ | |
99 | exit(1); \ | |
100 | } | |
101 | ||
102 | /* data type to hold offset in file and size */ | |
103 | typedef struct { | |
104 | void *data; | |
f85202c3 PN |
105 | size_t size; |
106 | size_t offset; | |
08af4d5c PN |
107 | } pos; |
108 | ||
109 | static unsigned char level = 0; | |
110 | static pos positions[16]; | |
111 | ||
112 | #define CURR_OFFSET (positions[level].offset) | |
113 | ||
114 | /* Hold a stack of errors */ | |
115 | typedef struct { | |
116 | char error[16][1024]; | |
f85202c3 PN |
117 | size_t offset[16]; |
118 | size_t level; | |
08af4d5c PN |
119 | } errors_t; |
120 | static errors_t errors; | |
121 | ||
122 | #define SHIFT_ERROR(provided_offset, ...) { \ | |
123 | sprintf(errors.error[errors.level], __VA_ARGS__); \ | |
124 | errors.offset[errors.level] = provided_offset; \ | |
125 | errors.level++; \ | |
126 | } | |
127 | ||
128 | /* Data type to hold opcode with optional key name an success status */ | |
129 | typedef struct { | |
130 | char* key; | |
131 | int type; | |
132 | char success; | |
133 | } entry; | |
134 | ||
135 | /* Global vars that are actally used as constants. The following double | |
136 | * values are used for double on-disk serialization, and are initialized | |
137 | * at runtime to avoid strange compiler optimizations. */ | |
138 | static double R_Zero, R_PosInf, R_NegInf, R_Nan; | |
139 | ||
140 | /* store string types for output */ | |
141 | static char types[256][16]; | |
142 | ||
29c8cf01 | 143 | /* Prototypes */ |
144 | uint64_t crc64(uint64_t crc, const unsigned char *s, uint64_t l); | |
145 | ||
e5f794ff S |
146 | /* Return true if 't' is a valid object type. */ |
147 | int checkType(unsigned char t) { | |
148 | /* In case a new object type is added, update the following | |
149 | * condition as necessary. */ | |
150 | return | |
151 | (t >= REDIS_HASH_ZIPMAP && t <= REDIS_HASH_ZIPLIST) || | |
152 | t <= REDIS_HASH || | |
153 | t >= REDIS_EXPIRETIME_MS; | |
154 | } | |
155 | ||
08af4d5c PN |
156 | /* when number of bytes to read is negative, do a peek */ |
157 | int readBytes(void *target, long num) { | |
158 | char peek = (num < 0) ? 1 : 0; | |
159 | num = (num < 0) ? -num : num; | |
160 | ||
161 | pos p = positions[level]; | |
162 | if (p.offset + num > p.size) { | |
163 | return 0; | |
164 | } else { | |
f85202c3 | 165 | memcpy(target, (void*)((size_t)p.data + p.offset), num); |
08af4d5c PN |
166 | if (!peek) positions[level].offset += num; |
167 | } | |
168 | return 1; | |
169 | } | |
170 | ||
171 | int processHeader() { | |
172 | char buf[10] = "_________"; | |
173 | int dump_version; | |
174 | ||
175 | if (!readBytes(buf, 9)) { | |
176 | ERROR("Cannot read header\n"); | |
177 | } | |
178 | ||
179 | /* expect the first 5 bytes to equal REDIS */ | |
180 | if (memcmp(buf,"REDIS",5) != 0) { | |
181 | ERROR("Wrong signature in header\n"); | |
182 | } | |
183 | ||
184 | dump_version = (int)strtol(buf + 5, NULL, 10); | |
29c8cf01 | 185 | if (dump_version < 1 || dump_version > 6) { |
08af4d5c PN |
186 | ERROR("Unknown RDB format version: %d\n", dump_version); |
187 | } | |
29c8cf01 | 188 | return dump_version; |
08af4d5c PN |
189 | } |
190 | ||
191 | int loadType(entry *e) { | |
192 | uint32_t offset = CURR_OFFSET; | |
193 | ||
194 | /* this byte needs to qualify as type */ | |
195 | unsigned char t; | |
196 | if (readBytes(&t, 1)) { | |
e5f794ff | 197 | if (checkType(t)) { |
08af4d5c PN |
198 | e->type = t; |
199 | return 1; | |
200 | } else { | |
201 | SHIFT_ERROR(offset, "Unknown type (0x%02x)", t); | |
202 | } | |
203 | } else { | |
204 | SHIFT_ERROR(offset, "Could not read type"); | |
205 | } | |
206 | ||
207 | /* failure */ | |
208 | return 0; | |
209 | } | |
210 | ||
211 | int peekType() { | |
212 | unsigned char t; | |
e5f794ff | 213 | if (readBytes(&t, -1) && (checkType(t))) |
2e63cfe2 | 214 | return t; |
08af4d5c PN |
215 | return -1; |
216 | } | |
217 | ||
218 | /* discard time, just consume the bytes */ | |
e5f794ff | 219 | int processTime(int type) { |
08af4d5c | 220 | uint32_t offset = CURR_OFFSET; |
e5f794ff S |
221 | unsigned char t[8]; |
222 | int timelen = (type == REDIS_EXPIRETIME_MS) ? 8 : 4; | |
223 | ||
224 | if (readBytes(t,timelen)) { | |
08af4d5c PN |
225 | return 1; |
226 | } else { | |
227 | SHIFT_ERROR(offset, "Could not read time"); | |
228 | } | |
229 | ||
230 | /* failure */ | |
231 | return 0; | |
232 | } | |
233 | ||
234 | uint32_t loadLength(int *isencoded) { | |
235 | unsigned char buf[2]; | |
236 | uint32_t len; | |
237 | int type; | |
238 | ||
239 | if (isencoded) *isencoded = 0; | |
240 | if (!readBytes(buf, 1)) return REDIS_RDB_LENERR; | |
241 | type = (buf[0] & 0xC0) >> 6; | |
242 | if (type == REDIS_RDB_6BITLEN) { | |
243 | /* Read a 6 bit len */ | |
244 | return buf[0] & 0x3F; | |
245 | } else if (type == REDIS_RDB_ENCVAL) { | |
246 | /* Read a 6 bit len encoding type */ | |
247 | if (isencoded) *isencoded = 1; | |
248 | return buf[0] & 0x3F; | |
249 | } else if (type == REDIS_RDB_14BITLEN) { | |
250 | /* Read a 14 bit len */ | |
251 | if (!readBytes(buf+1,1)) return REDIS_RDB_LENERR; | |
252 | return ((buf[0] & 0x3F) << 8) | buf[1]; | |
253 | } else { | |
254 | /* Read a 32 bit len */ | |
255 | if (!readBytes(&len, 4)) return REDIS_RDB_LENERR; | |
256 | return (unsigned int)ntohl(len); | |
257 | } | |
258 | } | |
259 | ||
260 | char *loadIntegerObject(int enctype) { | |
261 | uint32_t offset = CURR_OFFSET; | |
262 | unsigned char enc[4]; | |
263 | long long val; | |
264 | ||
265 | if (enctype == REDIS_RDB_ENC_INT8) { | |
266 | uint8_t v; | |
267 | if (!readBytes(enc, 1)) return NULL; | |
268 | v = enc[0]; | |
269 | val = (int8_t)v; | |
270 | } else if (enctype == REDIS_RDB_ENC_INT16) { | |
271 | uint16_t v; | |
272 | if (!readBytes(enc, 2)) return NULL; | |
273 | v = enc[0]|(enc[1]<<8); | |
274 | val = (int16_t)v; | |
275 | } else if (enctype == REDIS_RDB_ENC_INT32) { | |
276 | uint32_t v; | |
277 | if (!readBytes(enc, 4)) return NULL; | |
278 | v = enc[0]|(enc[1]<<8)|(enc[2]<<16)|(enc[3]<<24); | |
279 | val = (int32_t)v; | |
280 | } else { | |
281 | SHIFT_ERROR(offset, "Unknown integer encoding (0x%02x)", enctype); | |
282 | return NULL; | |
283 | } | |
284 | ||
285 | /* convert val into string */ | |
286 | char *buf; | |
287 | buf = malloc(sizeof(char) * 128); | |
288 | sprintf(buf, "%lld", val); | |
289 | return buf; | |
290 | } | |
291 | ||
292 | char* loadLzfStringObject() { | |
293 | unsigned int slen, clen; | |
294 | char *c, *s; | |
295 | ||
296 | if ((clen = loadLength(NULL)) == REDIS_RDB_LENERR) return NULL; | |
297 | if ((slen = loadLength(NULL)) == REDIS_RDB_LENERR) return NULL; | |
298 | ||
299 | c = malloc(clen); | |
300 | if (!readBytes(c, clen)) { | |
301 | free(c); | |
302 | return NULL; | |
303 | } | |
304 | ||
305 | s = malloc(slen+1); | |
306 | if (lzf_decompress(c,clen,s,slen) == 0) { | |
307 | free(c); free(s); | |
308 | return NULL; | |
309 | } | |
310 | ||
311 | free(c); | |
312 | return s; | |
313 | } | |
314 | ||
315 | /* returns NULL when not processable, char* when valid */ | |
316 | char* loadStringObject() { | |
317 | uint32_t offset = CURR_OFFSET; | |
318 | int isencoded; | |
319 | uint32_t len; | |
320 | ||
321 | len = loadLength(&isencoded); | |
322 | if (isencoded) { | |
323 | switch(len) { | |
324 | case REDIS_RDB_ENC_INT8: | |
325 | case REDIS_RDB_ENC_INT16: | |
326 | case REDIS_RDB_ENC_INT32: | |
327 | return loadIntegerObject(len); | |
328 | case REDIS_RDB_ENC_LZF: | |
329 | return loadLzfStringObject(); | |
330 | default: | |
331 | /* unknown encoding */ | |
332 | SHIFT_ERROR(offset, "Unknown string encoding (0x%02x)", len); | |
333 | return NULL; | |
334 | } | |
335 | } | |
336 | ||
337 | if (len == REDIS_RDB_LENERR) return NULL; | |
338 | ||
339 | char *buf = malloc(sizeof(char) * (len+1)); | |
340 | buf[len] = '\0'; | |
341 | if (!readBytes(buf, len)) { | |
342 | free(buf); | |
343 | return NULL; | |
344 | } | |
345 | return buf; | |
346 | } | |
347 | ||
348 | int processStringObject(char** store) { | |
349 | unsigned long offset = CURR_OFFSET; | |
350 | char *key = loadStringObject(); | |
351 | if (key == NULL) { | |
352 | SHIFT_ERROR(offset, "Error reading string object"); | |
353 | free(key); | |
354 | return 0; | |
355 | } | |
356 | ||
357 | if (store != NULL) { | |
358 | *store = key; | |
359 | } else { | |
360 | free(key); | |
361 | } | |
362 | return 1; | |
363 | } | |
364 | ||
365 | double* loadDoubleValue() { | |
366 | char buf[256]; | |
367 | unsigned char len; | |
368 | double* val; | |
369 | ||
370 | if (!readBytes(&len,1)) return NULL; | |
371 | ||
372 | val = malloc(sizeof(double)); | |
373 | switch(len) { | |
374 | case 255: *val = R_NegInf; return val; | |
375 | case 254: *val = R_PosInf; return val; | |
376 | case 253: *val = R_Nan; return val; | |
377 | default: | |
378 | if (!readBytes(buf, len)) { | |
379 | free(val); | |
380 | return NULL; | |
381 | } | |
382 | buf[len] = '\0'; | |
383 | sscanf(buf, "%lg", val); | |
384 | return val; | |
385 | } | |
386 | } | |
387 | ||
388 | int processDoubleValue(double** store) { | |
389 | unsigned long offset = CURR_OFFSET; | |
390 | double *val = loadDoubleValue(); | |
391 | if (val == NULL) { | |
392 | SHIFT_ERROR(offset, "Error reading double value"); | |
393 | free(val); | |
394 | return 0; | |
395 | } | |
396 | ||
397 | if (store != NULL) { | |
398 | *store = val; | |
399 | } else { | |
400 | free(val); | |
401 | } | |
402 | return 1; | |
403 | } | |
404 | ||
405 | int loadPair(entry *e) { | |
406 | uint32_t offset = CURR_OFFSET; | |
407 | uint32_t i; | |
408 | ||
409 | /* read key first */ | |
410 | char *key; | |
411 | if (processStringObject(&key)) { | |
412 | e->key = key; | |
413 | } else { | |
414 | SHIFT_ERROR(offset, "Error reading entry key"); | |
415 | return 0; | |
416 | } | |
417 | ||
418 | uint32_t length = 0; | |
419 | if (e->type == REDIS_LIST || | |
420 | e->type == REDIS_SET || | |
421 | e->type == REDIS_ZSET || | |
422 | e->type == REDIS_HASH) { | |
423 | if ((length = loadLength(NULL)) == REDIS_RDB_LENERR) { | |
424 | SHIFT_ERROR(offset, "Error reading %s length", types[e->type]); | |
425 | return 0; | |
426 | } | |
427 | } | |
428 | ||
429 | switch(e->type) { | |
430 | case REDIS_STRING: | |
2e63cfe2 | 431 | case REDIS_HASH_ZIPMAP: |
432 | case REDIS_LIST_ZIPLIST: | |
433 | case REDIS_SET_INTSET: | |
434 | case REDIS_ZSET_ZIPLIST: | |
f12d0224 | 435 | case REDIS_HASH_ZIPLIST: |
08af4d5c PN |
436 | if (!processStringObject(NULL)) { |
437 | SHIFT_ERROR(offset, "Error reading entry value"); | |
438 | return 0; | |
439 | } | |
440 | break; | |
441 | case REDIS_LIST: | |
442 | case REDIS_SET: | |
443 | for (i = 0; i < length; i++) { | |
444 | offset = CURR_OFFSET; | |
445 | if (!processStringObject(NULL)) { | |
446 | SHIFT_ERROR(offset, "Error reading element at index %d (length: %d)", i, length); | |
447 | return 0; | |
448 | } | |
449 | } | |
450 | break; | |
451 | case REDIS_ZSET: | |
452 | for (i = 0; i < length; i++) { | |
453 | offset = CURR_OFFSET; | |
454 | if (!processStringObject(NULL)) { | |
455 | SHIFT_ERROR(offset, "Error reading element key at index %d (length: %d)", i, length); | |
456 | return 0; | |
457 | } | |
458 | offset = CURR_OFFSET; | |
459 | if (!processDoubleValue(NULL)) { | |
460 | SHIFT_ERROR(offset, "Error reading element value at index %d (length: %d)", i, length); | |
461 | return 0; | |
462 | } | |
463 | } | |
464 | break; | |
465 | case REDIS_HASH: | |
466 | for (i = 0; i < length; i++) { | |
467 | offset = CURR_OFFSET; | |
468 | if (!processStringObject(NULL)) { | |
469 | SHIFT_ERROR(offset, "Error reading element key at index %d (length: %d)", i, length); | |
470 | return 0; | |
471 | } | |
472 | offset = CURR_OFFSET; | |
473 | if (!processStringObject(NULL)) { | |
474 | SHIFT_ERROR(offset, "Error reading element value at index %d (length: %d)", i, length); | |
475 | return 0; | |
476 | } | |
477 | } | |
478 | break; | |
479 | default: | |
480 | SHIFT_ERROR(offset, "Type not implemented"); | |
481 | return 0; | |
482 | } | |
483 | /* because we're done, we assume success */ | |
484 | e->success = 1; | |
485 | return 1; | |
486 | } | |
487 | ||
488 | entry loadEntry() { | |
489 | entry e = { NULL, -1, 0 }; | |
490 | uint32_t length, offset[4]; | |
491 | ||
492 | /* reset error container */ | |
493 | errors.level = 0; | |
494 | ||
495 | offset[0] = CURR_OFFSET; | |
496 | if (!loadType(&e)) { | |
497 | return e; | |
498 | } | |
499 | ||
500 | offset[1] = CURR_OFFSET; | |
501 | if (e.type == REDIS_SELECTDB) { | |
502 | if ((length = loadLength(NULL)) == REDIS_RDB_LENERR) { | |
503 | SHIFT_ERROR(offset[1], "Error reading database number"); | |
504 | return e; | |
505 | } | |
506 | if (length > 63) { | |
507 | SHIFT_ERROR(offset[1], "Database number out of range (%d)", length); | |
508 | return e; | |
509 | } | |
510 | } else if (e.type == REDIS_EOF) { | |
511 | if (positions[level].offset < positions[level].size) { | |
512 | SHIFT_ERROR(offset[0], "Unexpected EOF"); | |
513 | } else { | |
514 | e.success = 1; | |
515 | } | |
516 | return e; | |
517 | } else { | |
518 | /* optionally consume expire */ | |
e5f794ff S |
519 | if (e.type == REDIS_EXPIRETIME || |
520 | e.type == REDIS_EXPIRETIME_MS) { | |
521 | if (!processTime(e.type)) return e; | |
08af4d5c PN |
522 | if (!loadType(&e)) return e; |
523 | } | |
524 | ||
525 | offset[1] = CURR_OFFSET; | |
526 | if (!loadPair(&e)) { | |
527 | SHIFT_ERROR(offset[1], "Error for type %s", types[e.type]); | |
528 | return e; | |
529 | } | |
530 | } | |
531 | ||
532 | /* all entries are followed by a valid type: | |
533 | * e.g. a new entry, SELECTDB, EXPIRE, EOF */ | |
534 | offset[2] = CURR_OFFSET; | |
535 | if (peekType() == -1) { | |
536 | SHIFT_ERROR(offset[2], "Followed by invalid type"); | |
537 | SHIFT_ERROR(offset[0], "Error for type %s", types[e.type]); | |
538 | e.success = 0; | |
539 | } else { | |
540 | e.success = 1; | |
541 | } | |
542 | ||
543 | return e; | |
544 | } | |
545 | ||
546 | void printCentered(int indent, int width, char* body) { | |
547 | char head[256], tail[256]; | |
548 | memset(head, '\0', 256); | |
549 | memset(tail, '\0', 256); | |
550 | ||
551 | memset(head, '=', indent); | |
552 | memset(tail, '=', width - 2 - indent - strlen(body)); | |
553 | printf("%s %s %s\n", head, body, tail); | |
554 | } | |
555 | ||
7b30cc3a | 556 | void printValid(uint64_t ops, uint64_t bytes) { |
08af4d5c | 557 | char body[80]; |
a047bf52 | 558 | sprintf(body, "Processed %llu valid opcodes (in %llu bytes)", |
559 | (unsigned long long) ops, (unsigned long long) bytes); | |
08af4d5c PN |
560 | printCentered(4, 80, body); |
561 | } | |
562 | ||
7b30cc3a | 563 | void printSkipped(uint64_t bytes, uint64_t offset) { |
08af4d5c | 564 | char body[80]; |
a047bf52 | 565 | sprintf(body, "Skipped %llu bytes (resuming at 0x%08llx)", |
566 | (unsigned long long) bytes, (unsigned long long) offset); | |
08af4d5c PN |
567 | printCentered(4, 80, body); |
568 | } | |
569 | ||
570 | void printErrorStack(entry *e) { | |
571 | unsigned int i; | |
572 | char body[64]; | |
573 | ||
574 | if (e->type == -1) { | |
575 | sprintf(body, "Error trace"); | |
576 | } else if (e->type >= 253) { | |
577 | sprintf(body, "Error trace (%s)", types[e->type]); | |
578 | } else if (!e->key) { | |
579 | sprintf(body, "Error trace (%s: (unknown))", types[e->type]); | |
580 | } else { | |
581 | char tmp[41]; | |
582 | strncpy(tmp, e->key, 40); | |
583 | ||
584 | /* display truncation at the last 3 chars */ | |
585 | if (strlen(e->key) > 40) { | |
586 | memset(&tmp[37], '.', 3); | |
587 | } | |
588 | ||
589 | /* display unprintable characters as ? */ | |
590 | for (i = 0; i < strlen(tmp); i++) { | |
591 | if (tmp[i] <= 32) tmp[i] = '?'; | |
592 | } | |
593 | sprintf(body, "Error trace (%s: %s)", types[e->type], tmp); | |
594 | } | |
595 | ||
596 | printCentered(4, 80, body); | |
597 | ||
598 | /* display error stack */ | |
599 | for (i = 0; i < errors.level; i++) { | |
10c12171 | 600 | printf("0x%08lx - %s\n", |
601 | (unsigned long) errors.offset[i], errors.error[i]); | |
08af4d5c PN |
602 | } |
603 | } | |
604 | ||
605 | void process() { | |
7b30cc3a | 606 | uint64_t num_errors = 0, num_valid_ops = 0, num_valid_bytes = 0; |
08af4d5c | 607 | entry entry; |
29c8cf01 | 608 | int dump_version = processHeader(); |
609 | ||
610 | /* Exclude the final checksum for RDB >= 5. Will be checked at the end. */ | |
611 | if (dump_version >= 5) { | |
612 | if (positions[0].size < 8) { | |
613 | printf("RDB version >= 5 but no room for checksum.\n"); | |
614 | exit(1); | |
615 | } | |
616 | positions[0].size -= 8;; | |
617 | } | |
08af4d5c PN |
618 | |
619 | level = 1; | |
620 | while(positions[0].offset < positions[0].size) { | |
621 | positions[1] = positions[0]; | |
622 | ||
623 | entry = loadEntry(); | |
624 | if (!entry.success) { | |
625 | printValid(num_valid_ops, num_valid_bytes); | |
626 | printErrorStack(&entry); | |
627 | num_errors++; | |
628 | num_valid_ops = 0; | |
629 | num_valid_bytes = 0; | |
630 | ||
631 | /* search for next valid entry */ | |
7b30cc3a PN |
632 | uint64_t offset = positions[0].offset + 1; |
633 | int i = 0; | |
634 | ||
08af4d5c PN |
635 | while (!entry.success && offset < positions[0].size) { |
636 | positions[1].offset = offset; | |
637 | ||
638 | /* find 3 consecutive valid entries */ | |
639 | for (i = 0; i < 3; i++) { | |
640 | entry = loadEntry(); | |
641 | if (!entry.success) break; | |
642 | } | |
643 | /* check if we found 3 consecutive valid entries */ | |
644 | if (i < 3) { | |
645 | offset++; | |
646 | } | |
647 | } | |
648 | ||
649 | /* print how many bytes we have skipped to find a new valid opcode */ | |
650 | if (offset < positions[0].size) { | |
651 | printSkipped(offset - positions[0].offset, offset); | |
652 | } | |
653 | ||
654 | positions[0].offset = offset; | |
655 | } else { | |
656 | num_valid_ops++; | |
657 | num_valid_bytes += positions[1].offset - positions[0].offset; | |
658 | ||
659 | /* advance position */ | |
660 | positions[0] = positions[1]; | |
661 | } | |
046f70f7 | 662 | free(entry.key); |
08af4d5c PN |
663 | } |
664 | ||
665 | /* because there is another potential error, | |
666 | * print how many valid ops we have processed */ | |
667 | printValid(num_valid_ops, num_valid_bytes); | |
668 | ||
669 | /* expect an eof */ | |
670 | if (entry.type != REDIS_EOF) { | |
671 | /* last byte should be EOF, add error */ | |
672 | errors.level = 0; | |
673 | SHIFT_ERROR(positions[0].offset, "Expected EOF, got %s", types[entry.type]); | |
674 | ||
675 | /* this is an EOF error so reset type */ | |
676 | entry.type = -1; | |
677 | printErrorStack(&entry); | |
678 | ||
679 | num_errors++; | |
680 | } | |
681 | ||
29c8cf01 | 682 | /* Verify checksum */ |
683 | if (dump_version >= 5) { | |
684 | uint64_t crc = crc64(0,positions[0].data,positions[0].size); | |
685 | uint64_t crc2; | |
686 | unsigned char *p = (unsigned char*)positions[0].data+positions[0].size; | |
687 | crc2 = ((uint64_t)p[0] << 0) | | |
688 | ((uint64_t)p[1] << 8) | | |
689 | ((uint64_t)p[2] << 16) | | |
690 | ((uint64_t)p[3] << 24) | | |
691 | ((uint64_t)p[4] << 32) | | |
692 | ((uint64_t)p[5] << 40) | | |
693 | ((uint64_t)p[6] << 48) | | |
694 | ((uint64_t)p[7] << 56); | |
695 | if (crc != crc2) { | |
696 | SHIFT_ERROR(positions[0].offset, "RDB CRC64 does not match."); | |
697 | } else { | |
698 | printf("CRC64 checksum is OK\n"); | |
699 | } | |
700 | } | |
701 | ||
08af4d5c | 702 | /* print summary on errors */ |
7b30cc3a | 703 | if (num_errors) { |
08af4d5c | 704 | printf("\n"); |
a047bf52 | 705 | printf("Total unprocessable opcodes: %llu\n", |
706 | (unsigned long long) num_errors); | |
08af4d5c PN |
707 | } |
708 | } | |
709 | ||
710 | int main(int argc, char **argv) { | |
711 | /* expect the first argument to be the dump file */ | |
712 | if (argc <= 1) { | |
713 | printf("Usage: %s <dump.rdb>\n", argv[0]); | |
714 | exit(0); | |
715 | } | |
716 | ||
717 | int fd; | |
f85202c3 | 718 | off_t size; |
08af4d5c PN |
719 | struct stat stat; |
720 | void *data; | |
721 | ||
722 | fd = open(argv[1], O_RDONLY); | |
723 | if (fd < 1) { | |
724 | ERROR("Cannot open file: %s\n", argv[1]); | |
725 | } | |
726 | if (fstat(fd, &stat) == -1) { | |
727 | ERROR("Cannot stat: %s\n", argv[1]); | |
728 | } else { | |
729 | size = stat.st_size; | |
730 | } | |
731 | ||
f85202c3 PN |
732 | if (sizeof(size_t) == sizeof(int32_t) && size >= INT_MAX) { |
733 | ERROR("Cannot check dump files >2GB on a 32-bit platform\n"); | |
734 | } | |
735 | ||
08af4d5c PN |
736 | data = mmap(NULL, size, PROT_READ, MAP_SHARED, fd, 0); |
737 | if (data == MAP_FAILED) { | |
738 | ERROR("Cannot mmap: %s\n", argv[1]); | |
739 | } | |
740 | ||
741 | /* Initialize static vars */ | |
742 | positions[0].data = data; | |
743 | positions[0].size = size; | |
744 | positions[0].offset = 0; | |
745 | errors.level = 0; | |
746 | ||
747 | /* Object types */ | |
748 | sprintf(types[REDIS_STRING], "STRING"); | |
749 | sprintf(types[REDIS_LIST], "LIST"); | |
750 | sprintf(types[REDIS_SET], "SET"); | |
751 | sprintf(types[REDIS_ZSET], "ZSET"); | |
752 | sprintf(types[REDIS_HASH], "HASH"); | |
753 | ||
754 | /* Object types only used for dumping to disk */ | |
755 | sprintf(types[REDIS_EXPIRETIME], "EXPIRETIME"); | |
756 | sprintf(types[REDIS_SELECTDB], "SELECTDB"); | |
757 | sprintf(types[REDIS_EOF], "EOF"); | |
758 | ||
759 | /* Double constants initialization */ | |
760 | R_Zero = 0.0; | |
761 | R_PosInf = 1.0/R_Zero; | |
762 | R_NegInf = -1.0/R_Zero; | |
763 | R_Nan = R_Zero/R_Zero; | |
764 | ||
765 | process(); | |
766 | ||
767 | munmap(data, size); | |
768 | close(fd); | |
769 | return 0; | |
770 | } |