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
39void createSharedObjects(void);
40void rdbLoadProgressCallback(rio *r, const void *buf, size_t len);
41int rdbCheckMode = 0;
42
43struct {
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
68char *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
81char *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' */
103void 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. */
111void 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. */
138void 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. */
153void 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. */
165void 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
174void 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. */
191int 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
371eoferr: /* 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 }
377err:
378 if (closefile) fclose(fp);
379 stopLoading(0);
380 return 1;
381}
382
383static 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. */
409int 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