1 | /* |
2 | * Copyright (c) 2016, 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 "mt19937-64.h" |
31 | #include "server.h" |
32 | #include "rdb.h" |
33 | |
34 | #include <stdarg.h> |
35 | #include <sys/time.h> |
36 | #include <unistd.h> |
37 | #include <sys/stat.h> |
38 | |
39 | void createSharedObjects(void); |
40 | void rdbLoadProgressCallback(rio *r, const void *buf, size_t len); |
41 | int rdbCheckMode = 0; |
42 | |
43 | struct { |
44 | rio *rio; |
45 | robj *key; /* Current key we are reading. */ |
46 | int key_type; /* Current key type if != -1. */ |
47 | unsigned long keys; /* Number of keys processed. */ |
48 | unsigned long expires; /* Number of keys with an expire. */ |
49 | unsigned long already_expired; /* Number of keys already expired. */ |
50 | int doing; /* The state while reading the RDB. */ |
51 | int error_set; /* True if error is populated. */ |
52 | char error[1024]; |
53 | } rdbstate; |
54 | |
55 | /* At every loading step try to remember what we were about to do, so that |
56 | * we can log this information when an error is encountered. */ |
57 | #define RDB_CHECK_DOING_START 0 |
58 | #define RDB_CHECK_DOING_READ_TYPE 1 |
59 | #define RDB_CHECK_DOING_READ_EXPIRE 2 |
60 | #define RDB_CHECK_DOING_READ_KEY 3 |
61 | #define RDB_CHECK_DOING_READ_OBJECT_VALUE 4 |
62 | #define RDB_CHECK_DOING_CHECK_SUM 5 |
63 | #define RDB_CHECK_DOING_READ_LEN 6 |
64 | #define RDB_CHECK_DOING_READ_AUX 7 |
65 | #define RDB_CHECK_DOING_READ_MODULE_AUX 8 |
66 | #define RDB_CHECK_DOING_READ_FUNCTIONS 9 |
67 | |
68 | char *rdb_check_doing_string[] = { |
69 | "start" , |
70 | "read-type" , |
71 | "read-expire" , |
72 | "read-key" , |
73 | "read-object-value" , |
74 | "check-sum" , |
75 | "read-len" , |
76 | "read-aux" , |
77 | "read-module-aux" , |
78 | "read-functions" |
79 | }; |
80 | |
81 | char *rdb_type_string[] = { |
82 | "string" , |
83 | "list-linked" , |
84 | "set-hashtable" , |
85 | "zset-v1" , |
86 | "hash-hashtable" , |
87 | "zset-v2" , |
88 | "module-value" , |
89 | "" ,"" , |
90 | "hash-zipmap" , |
91 | "list-ziplist" , |
92 | "set-intset" , |
93 | "zset-ziplist" , |
94 | "hash-ziplist" , |
95 | "quicklist" , |
96 | "stream" , |
97 | "hash-listpack" , |
98 | "zset-listpack" , |
99 | "quicklist-v2" |
100 | }; |
101 | |
102 | /* Show a few stats collected into 'rdbstate' */ |
103 | void rdbShowGenericInfo(void) { |
104 | printf("[info] %lu keys read\n" , rdbstate.keys); |
105 | printf("[info] %lu expires\n" , rdbstate.expires); |
106 | printf("[info] %lu already expired\n" , rdbstate.already_expired); |
107 | } |
108 | |
109 | /* Called on RDB errors. Provides details about the RDB and the offset |
110 | * we were when the error was detected. */ |
111 | void rdbCheckError(const char *fmt, ...) { |
112 | char msg[1024]; |
113 | va_list ap; |
114 | |
115 | va_start(ap, fmt); |
116 | vsnprintf(msg, sizeof(msg), fmt, ap); |
117 | va_end(ap); |
118 | |
119 | printf("--- RDB ERROR DETECTED ---\n" ); |
120 | printf("[offset %llu] %s\n" , |
121 | (unsigned long long) (rdbstate.rio ? |
122 | rdbstate.rio->processed_bytes : 0), msg); |
123 | printf("[additional info] While doing: %s\n" , |
124 | rdb_check_doing_string[rdbstate.doing]); |
125 | if (rdbstate.key) |
126 | printf("[additional info] Reading key '%s'\n" , |
127 | (char*)rdbstate.key->ptr); |
128 | if (rdbstate.key_type != -1) |
129 | printf("[additional info] Reading type %d (%s)\n" , |
130 | rdbstate.key_type, |
131 | ((unsigned)rdbstate.key_type < |
132 | sizeof(rdb_type_string)/sizeof(char*)) ? |
133 | rdb_type_string[rdbstate.key_type] : "unknown" ); |
134 | rdbShowGenericInfo(); |
135 | } |
136 | |
137 | /* Print information during RDB checking. */ |
138 | void rdbCheckInfo(const char *fmt, ...) { |
139 | char msg[1024]; |
140 | va_list ap; |
141 | |
142 | va_start(ap, fmt); |
143 | vsnprintf(msg, sizeof(msg), fmt, ap); |
144 | va_end(ap); |
145 | |
146 | printf("[offset %llu] %s\n" , |
147 | (unsigned long long) (rdbstate.rio ? |
148 | rdbstate.rio->processed_bytes : 0), msg); |
149 | } |
150 | |
151 | /* Used inside rdb.c in order to log specific errors happening inside |
152 | * the RDB loading internals. */ |
153 | void rdbCheckSetError(const char *fmt, ...) { |
154 | va_list ap; |
155 | |
156 | va_start(ap, fmt); |
157 | vsnprintf(rdbstate.error, sizeof(rdbstate.error), fmt, ap); |
158 | va_end(ap); |
159 | rdbstate.error_set = 1; |
160 | } |
161 | |
162 | /* During RDB check we setup a special signal handler for memory violations |
163 | * and similar conditions, so that we can log the offending part of the RDB |
164 | * if the crash is due to broken content. */ |
165 | void rdbCheckHandleCrash(int sig, siginfo_t *info, void *secret) { |
166 | UNUSED(sig); |
167 | UNUSED(info); |
168 | UNUSED(secret); |
169 | |
170 | rdbCheckError("Server crash checking the specified RDB file!" ); |
171 | exit(1); |
172 | } |
173 | |
174 | void rdbCheckSetupSignals(void) { |
175 | struct sigaction act; |
176 | |
177 | sigemptyset(&act.sa_mask); |
178 | act.sa_flags = SA_NODEFER | SA_RESETHAND | SA_SIGINFO; |
179 | act.sa_sigaction = rdbCheckHandleCrash; |
180 | sigaction(SIGSEGV, &act, NULL); |
181 | sigaction(SIGBUS, &act, NULL); |
182 | sigaction(SIGFPE, &act, NULL); |
183 | sigaction(SIGILL, &act, NULL); |
184 | sigaction(SIGABRT, &act, NULL); |
185 | } |
186 | |
187 | /* Check the specified RDB file. Return 0 if the RDB looks sane, otherwise |
188 | * 1 is returned. |
189 | * The file is specified as a filename in 'rdbfilename' if 'fp' is not NULL, |
190 | * otherwise the already open file 'fp' is checked. */ |
191 | int redis_check_rdb(char *rdbfilename, FILE *fp) { |
192 | uint64_t dbid; |
193 | int selected_dbid = -1; |
194 | int type, rdbver; |
195 | char buf[1024]; |
196 | long long expiretime, now = mstime(); |
197 | static rio rdb; /* Pointed by global struct riostate. */ |
198 | struct stat sb; |
199 | |
200 | int closefile = (fp == NULL); |
201 | if (fp == NULL && (fp = fopen(rdbfilename,"r" )) == NULL) return 1; |
202 | |
203 | if (fstat(fileno(fp), &sb) == -1) |
204 | sb.st_size = 0; |
205 | |
206 | startLoadingFile(sb.st_size, rdbfilename, RDBFLAGS_NONE); |
207 | rioInitWithFile(&rdb,fp); |
208 | rdbstate.rio = &rdb; |
209 | rdb.update_cksum = rdbLoadProgressCallback; |
210 | if (rioRead(&rdb,buf,9) == 0) goto eoferr; |
211 | buf[9] = '\0'; |
212 | if (memcmp(buf,"REDIS" ,5) != 0) { |
213 | rdbCheckError("Wrong signature trying to load DB from file" ); |
214 | goto err; |
215 | } |
216 | rdbver = atoi(buf+5); |
217 | if (rdbver < 1 || rdbver > RDB_VERSION) { |
218 | rdbCheckError("Can't handle RDB format version %d" ,rdbver); |
219 | goto err; |
220 | } |
221 | |
222 | expiretime = -1; |
223 | while(1) { |
224 | robj *key, *val; |
225 | |
226 | /* Read type. */ |
227 | rdbstate.doing = RDB_CHECK_DOING_READ_TYPE; |
228 | if ((type = rdbLoadType(&rdb)) == -1) goto eoferr; |
229 | |
230 | /* Handle special types. */ |
231 | if (type == RDB_OPCODE_EXPIRETIME) { |
232 | rdbstate.doing = RDB_CHECK_DOING_READ_EXPIRE; |
233 | /* EXPIRETIME: load an expire associated with the next key |
234 | * to load. Note that after loading an expire we need to |
235 | * load the actual type, and continue. */ |
236 | expiretime = rdbLoadTime(&rdb); |
237 | expiretime *= 1000; |
238 | if (rioGetReadError(&rdb)) goto eoferr; |
239 | continue; /* Read next opcode. */ |
240 | } else if (type == RDB_OPCODE_EXPIRETIME_MS) { |
241 | /* EXPIRETIME_MS: milliseconds precision expire times introduced |
242 | * with RDB v3. Like EXPIRETIME but no with more precision. */ |
243 | rdbstate.doing = RDB_CHECK_DOING_READ_EXPIRE; |
244 | expiretime = rdbLoadMillisecondTime(&rdb, rdbver); |
245 | if (rioGetReadError(&rdb)) goto eoferr; |
246 | continue; /* Read next opcode. */ |
247 | } else if (type == RDB_OPCODE_FREQ) { |
248 | /* FREQ: LFU frequency. */ |
249 | uint8_t byte; |
250 | if (rioRead(&rdb,&byte,1) == 0) goto eoferr; |
251 | continue; /* Read next opcode. */ |
252 | } else if (type == RDB_OPCODE_IDLE) { |
253 | /* IDLE: LRU idle time. */ |
254 | if (rdbLoadLen(&rdb,NULL) == RDB_LENERR) goto eoferr; |
255 | continue; /* Read next opcode. */ |
256 | } else if (type == RDB_OPCODE_EOF) { |
257 | /* EOF: End of file, exit the main loop. */ |
258 | break; |
259 | } else if (type == RDB_OPCODE_SELECTDB) { |
260 | /* SELECTDB: Select the specified database. */ |
261 | rdbstate.doing = RDB_CHECK_DOING_READ_LEN; |
262 | if ((dbid = rdbLoadLen(&rdb,NULL)) == RDB_LENERR) |
263 | goto eoferr; |
264 | rdbCheckInfo("Selecting DB ID %llu" , (unsigned long long)dbid); |
265 | selected_dbid = dbid; |
266 | continue; /* Read type again. */ |
267 | } else if (type == RDB_OPCODE_RESIZEDB) { |
268 | /* RESIZEDB: Hint about the size of the keys in the currently |
269 | * selected data base, in order to avoid useless rehashing. */ |
270 | uint64_t db_size, expires_size; |
271 | rdbstate.doing = RDB_CHECK_DOING_READ_LEN; |
272 | if ((db_size = rdbLoadLen(&rdb,NULL)) == RDB_LENERR) |
273 | goto eoferr; |
274 | if ((expires_size = rdbLoadLen(&rdb,NULL)) == RDB_LENERR) |
275 | goto eoferr; |
276 | continue; /* Read type again. */ |
277 | } else if (type == RDB_OPCODE_AUX) { |
278 | /* AUX: generic string-string fields. Use to add state to RDB |
279 | * which is backward compatible. Implementations of RDB loading |
280 | * are required to skip AUX fields they don't understand. |
281 | * |
282 | * An AUX field is composed of two strings: key and value. */ |
283 | robj *auxkey, *auxval; |
284 | rdbstate.doing = RDB_CHECK_DOING_READ_AUX; |
285 | if ((auxkey = rdbLoadStringObject(&rdb)) == NULL) goto eoferr; |
286 | if ((auxval = rdbLoadStringObject(&rdb)) == NULL) { |
287 | decrRefCount(auxkey); |
288 | goto eoferr; |
289 | } |
290 | |
291 | rdbCheckInfo("AUX FIELD %s = '%s'" , |
292 | (char*)auxkey->ptr, (char*)auxval->ptr); |
293 | decrRefCount(auxkey); |
294 | decrRefCount(auxval); |
295 | continue; /* Read type again. */ |
296 | } else if (type == RDB_OPCODE_MODULE_AUX) { |
297 | /* AUX: Auxiliary data for modules. */ |
298 | uint64_t moduleid, when_opcode, when; |
299 | rdbstate.doing = RDB_CHECK_DOING_READ_MODULE_AUX; |
300 | if ((moduleid = rdbLoadLen(&rdb,NULL)) == RDB_LENERR) goto eoferr; |
301 | if ((when_opcode = rdbLoadLen(&rdb,NULL)) == RDB_LENERR) goto eoferr; |
302 | if ((when = rdbLoadLen(&rdb,NULL)) == RDB_LENERR) goto eoferr; |
303 | if (when_opcode != RDB_MODULE_OPCODE_UINT) { |
304 | rdbCheckError("bad when_opcode" ); |
305 | goto err; |
306 | } |
307 | |
308 | char name[10]; |
309 | moduleTypeNameByID(name,moduleid); |
310 | rdbCheckInfo("MODULE AUX for: %s" , name); |
311 | |
312 | robj *o = rdbLoadCheckModuleValue(&rdb,name); |
313 | decrRefCount(o); |
314 | continue; /* Read type again. */ |
315 | } else if (type == RDB_OPCODE_FUNCTION || type == RDB_OPCODE_FUNCTION2) { |
316 | sds err = NULL; |
317 | rdbstate.doing = RDB_CHECK_DOING_READ_FUNCTIONS; |
318 | if (rdbFunctionLoad(&rdb, rdbver, NULL, type, 0, &err) != C_OK) { |
319 | rdbCheckError("Failed loading library, %s" , err); |
320 | sdsfree(err); |
321 | goto err; |
322 | } |
323 | continue; |
324 | } else { |
325 | if (!rdbIsObjectType(type)) { |
326 | rdbCheckError("Invalid object type: %d" , type); |
327 | goto err; |
328 | } |
329 | rdbstate.key_type = type; |
330 | } |
331 | |
332 | /* Read key */ |
333 | rdbstate.doing = RDB_CHECK_DOING_READ_KEY; |
334 | if ((key = rdbLoadStringObject(&rdb)) == NULL) goto eoferr; |
335 | rdbstate.key = key; |
336 | rdbstate.keys++; |
337 | /* Read value */ |
338 | rdbstate.doing = RDB_CHECK_DOING_READ_OBJECT_VALUE; |
339 | if ((val = rdbLoadObject(type,&rdb,key->ptr,selected_dbid,NULL)) == NULL) goto eoferr; |
340 | /* Check if the key already expired. */ |
341 | if (expiretime != -1 && expiretime < now) |
342 | rdbstate.already_expired++; |
343 | if (expiretime != -1) rdbstate.expires++; |
344 | rdbstate.key = NULL; |
345 | decrRefCount(key); |
346 | decrRefCount(val); |
347 | rdbstate.key_type = -1; |
348 | expiretime = -1; |
349 | } |
350 | /* Verify the checksum if RDB version is >= 5 */ |
351 | if (rdbver >= 5 && server.rdb_checksum) { |
352 | uint64_t cksum, expected = rdb.cksum; |
353 | |
354 | rdbstate.doing = RDB_CHECK_DOING_CHECK_SUM; |
355 | if (rioRead(&rdb,&cksum,8) == 0) goto eoferr; |
356 | memrev64ifbe(&cksum); |
357 | if (cksum == 0) { |
358 | rdbCheckInfo("RDB file was saved with checksum disabled: no check performed." ); |
359 | } else if (cksum != expected) { |
360 | rdbCheckError("RDB CRC error" ); |
361 | goto err; |
362 | } else { |
363 | rdbCheckInfo("Checksum OK" ); |
364 | } |
365 | } |
366 | |
367 | if (closefile) fclose(fp); |
368 | stopLoading(1); |
369 | return 0; |
370 | |
371 | eoferr: /* unexpected end of file is handled here with a fatal exit */ |
372 | if (rdbstate.error_set) { |
373 | rdbCheckError(rdbstate.error); |
374 | } else { |
375 | rdbCheckError("Unexpected EOF reading RDB file" ); |
376 | } |
377 | err: |
378 | if (closefile) fclose(fp); |
379 | stopLoading(0); |
380 | return 1; |
381 | } |
382 | |
383 | static sds checkRdbVersion(void) { |
384 | sds version; |
385 | version = sdscatprintf(sdsempty(), "%s" , REDIS_VERSION); |
386 | |
387 | /* Add git commit and working tree status when available */ |
388 | if (strtoll(redisGitSHA1(),NULL,16)) { |
389 | version = sdscatprintf(version, " (git:%s" , redisGitSHA1()); |
390 | if (strtoll(redisGitDirty(),NULL,10)) |
391 | version = sdscatprintf(version, "-dirty" ); |
392 | version = sdscat(version, ")" ); |
393 | } |
394 | return version; |
395 | } |
396 | |
397 | /* RDB check main: called form server.c when Redis is executed with the |
398 | * redis-check-rdb alias, on during RDB loading errors. |
399 | * |
400 | * The function works in two ways: can be called with argc/argv as a |
401 | * standalone executable, or called with a non NULL 'fp' argument if we |
402 | * already have an open file to check. This happens when the function |
403 | * is used to check an RDB preamble inside an AOF file. |
404 | * |
405 | * When called with fp = NULL, the function never returns, but exits with the |
406 | * status code according to success (RDB is sane) or error (RDB is corrupted). |
407 | * Otherwise if called with a non NULL fp, the function returns C_OK or |
408 | * C_ERR depending on the success or failure. */ |
409 | int redis_check_rdb_main(int argc, char **argv, FILE *fp) { |
410 | struct timeval tv; |
411 | |
412 | if (argc != 2 && fp == NULL) { |
413 | fprintf(stderr, "Usage: %s <rdb-file-name>\n" , argv[0]); |
414 | exit(1); |
415 | } else if (!strcmp(argv[1],"-v" ) || !strcmp(argv[1], "--version" )) { |
416 | sds version = checkRdbVersion(); |
417 | printf("redis-check-rdb %s\n" , version); |
418 | sdsfree(version); |
419 | exit(0); |
420 | } |
421 | |
422 | gettimeofday(&tv, NULL); |
423 | init_genrand64(((long long) tv.tv_sec * 1000000 + tv.tv_usec) ^ getpid()); |
424 | |
425 | /* In order to call the loading functions we need to create the shared |
426 | * integer objects, however since this function may be called from |
427 | * an already initialized Redis instance, check if we really need to. */ |
428 | if (shared.integers[0] == NULL) |
429 | createSharedObjects(); |
430 | server.loading_process_events_interval_bytes = 0; |
431 | server.sanitize_dump_payload = SANITIZE_DUMP_YES; |
432 | rdbCheckMode = 1; |
433 | rdbCheckInfo("Checking RDB file %s" , argv[1]); |
434 | rdbCheckSetupSignals(); |
435 | int retval = redis_check_rdb(argv[1],fp); |
436 | if (retval == 0) { |
437 | rdbCheckInfo("\\o/ RDB looks OK! \\o/" ); |
438 | rdbShowGenericInfo(); |
439 | } |
440 | if (fp) return (retval == 0) ? C_OK : C_ERR; |
441 | exit(retval); |
442 | } |
443 | |