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 "server.h"
31 #include "rdb.h"
32
33 #include <stdarg.h>
34
35 void createSharedObjects(void);
36 void rdbLoadProgressCallback(rio *r, const void *buf, size_t len);
37 long long rdbLoadMillisecondTime(rio *rdb);
38 int rdbCheckMode = 0;
39
40 struct {
41 rio *rio;
42 robj *key; /* Current key we are reading. */
43 int key_type; /* Current key type if != -1. */
44 unsigned long keys; /* Number of keys processed. */
45 unsigned long expires; /* Number of keys with an expire. */
46 unsigned long already_expired; /* Number of keys already expired. */
47 int doing; /* The state while reading the RDB. */
48 int error_set; /* True if error is populated. */
49 char error[1024];
50 } rdbstate;
51
52 /* At every loading step try to remember what we were about to do, so that
53 * we can log this information when an error is encountered. */
54 #define RDB_CHECK_DOING_START 0
55 #define RDB_CHECK_DOING_READ_TYPE 1
56 #define RDB_CHECK_DOING_READ_EXPIRE 2
57 #define RDB_CHECK_DOING_READ_KEY 3
58 #define RDB_CHECK_DOING_READ_OBJECT_VALUE 4
59 #define RDB_CHECK_DOING_CHECK_SUM 5
60 #define RDB_CHECK_DOING_READ_LEN 6
61 #define RDB_CHECK_DOING_READ_AUX 7
62
63 char *rdb_check_doing_string[] = {
64 "start",
65 "read-type",
66 "read-expire",
67 "read-key",
68 "read-object-value",
69 "check-sum",
70 "read-len",
71 "read-aux"
72 };
73
74 char *rdb_type_string[] = {
75 "string",
76 "list-linked",
77 "set-hashtable",
78 "zset-v1",
79 "hash-hashtable",
80 "zset-v2",
81 "module-value",
82 "","",
83 "hash-zipmap",
84 "list-ziplist",
85 "set-intset",
86 "zset-ziplist",
87 "hash-ziplist",
88 "quicklist"
89 };
90
91 /* Show a few stats collected into 'rdbstate' */
rdbShowGenericInfo(void)92 void rdbShowGenericInfo(void) {
93 printf("[info] %lu keys read\n", rdbstate.keys);
94 printf("[info] %lu expires\n", rdbstate.expires);
95 printf("[info] %lu already expired\n", rdbstate.already_expired);
96 }
97
98 /* Called on RDB errors. Provides details about the RDB and the offset
99 * we were when the error was detected. */
rdbCheckError(const char * fmt,...)100 void rdbCheckError(const char *fmt, ...) {
101 char msg[1024];
102 va_list ap;
103
104 va_start(ap, fmt);
105 vsnprintf(msg, sizeof(msg), fmt, ap);
106 va_end(ap);
107
108 printf("--- RDB ERROR DETECTED ---\n");
109 printf("[offset %llu] %s\n",
110 (unsigned long long) (rdbstate.rio ?
111 rdbstate.rio->processed_bytes : 0), msg);
112 printf("[additional info] While doing: %s\n",
113 rdb_check_doing_string[rdbstate.doing]);
114 if (rdbstate.key)
115 printf("[additional info] Reading key '%s'\n",
116 (char*)rdbstate.key->ptr);
117 if (rdbstate.key_type != -1)
118 printf("[additional info] Reading type %d (%s)\n",
119 rdbstate.key_type,
120 ((unsigned)rdbstate.key_type <
121 sizeof(rdb_type_string)/sizeof(char*)) ?
122 rdb_type_string[rdbstate.key_type] : "unknown");
123 rdbShowGenericInfo();
124 }
125
126 /* Print informations during RDB checking. */
rdbCheckInfo(const char * fmt,...)127 void rdbCheckInfo(const char *fmt, ...) {
128 char msg[1024];
129 va_list ap;
130
131 va_start(ap, fmt);
132 vsnprintf(msg, sizeof(msg), fmt, ap);
133 va_end(ap);
134
135 printf("[offset %llu] %s\n",
136 (unsigned long long) (rdbstate.rio ?
137 rdbstate.rio->processed_bytes : 0), msg);
138 }
139
140 /* Used inside rdb.c in order to log specific errors happening inside
141 * the RDB loading internals. */
rdbCheckSetError(const char * fmt,...)142 void rdbCheckSetError(const char *fmt, ...) {
143 va_list ap;
144
145 va_start(ap, fmt);
146 vsnprintf(rdbstate.error, sizeof(rdbstate.error), fmt, ap);
147 va_end(ap);
148 rdbstate.error_set = 1;
149 }
150
151 /* During RDB check we setup a special signal handler for memory violations
152 * and similar conditions, so that we can log the offending part of the RDB
153 * if the crash is due to broken content. */
rdbCheckHandleCrash(int sig,siginfo_t * info,void * secret)154 void rdbCheckHandleCrash(int sig, siginfo_t *info, void *secret) {
155 UNUSED(sig);
156 UNUSED(info);
157 UNUSED(secret);
158
159 rdbCheckError("Server crash checking the specified RDB file!");
160 exit(1);
161 }
162
rdbCheckSetupSignals(void)163 void rdbCheckSetupSignals(void) {
164 struct sigaction act;
165
166 sigemptyset(&act.sa_mask);
167 act.sa_flags = SA_NODEFER | SA_RESETHAND | SA_SIGINFO;
168 act.sa_sigaction = rdbCheckHandleCrash;
169 sigaction(SIGSEGV, &act, NULL);
170 sigaction(SIGBUS, &act, NULL);
171 sigaction(SIGFPE, &act, NULL);
172 sigaction(SIGILL, &act, NULL);
173 }
174
175 /* Check the specified RDB file. */
redis_check_rdb(char * rdbfilename)176 int redis_check_rdb(char *rdbfilename) {
177 uint64_t dbid;
178 int type, rdbver;
179 char buf[1024];
180 long long expiretime, now = mstime();
181 FILE *fp;
182 rio rdb;
183
184 if ((fp = fopen(rdbfilename,"r")) == NULL) return C_ERR;
185
186 rioInitWithFile(&rdb,fp);
187 rdbstate.rio = &rdb;
188 rdb.update_cksum = rdbLoadProgressCallback;
189 if (rioRead(&rdb,buf,9) == 0) goto eoferr;
190 buf[9] = '\0';
191 if (memcmp(buf,"REDIS",5) != 0) {
192 rdbCheckError("Wrong signature trying to load DB from file");
193 return 1;
194 }
195 rdbver = atoi(buf+5);
196 if (rdbver < 1 || rdbver > RDB_VERSION) {
197 rdbCheckError("Can't handle RDB format version %d",rdbver);
198 return 1;
199 }
200
201 startLoading(fp);
202 while(1) {
203 robj *key, *val;
204 expiretime = -1;
205
206 /* Read type. */
207 rdbstate.doing = RDB_CHECK_DOING_READ_TYPE;
208 if ((type = rdbLoadType(&rdb)) == -1) goto eoferr;
209
210 /* Handle special types. */
211 if (type == RDB_OPCODE_EXPIRETIME) {
212 rdbstate.doing = RDB_CHECK_DOING_READ_EXPIRE;
213 /* EXPIRETIME: load an expire associated with the next key
214 * to load. Note that after loading an expire we need to
215 * load the actual type, and continue. */
216 if ((expiretime = rdbLoadTime(&rdb)) == -1) goto eoferr;
217 /* We read the time so we need to read the object type again. */
218 rdbstate.doing = RDB_CHECK_DOING_READ_TYPE;
219 if ((type = rdbLoadType(&rdb)) == -1) goto eoferr;
220 /* the EXPIRETIME opcode specifies time in seconds, so convert
221 * into milliseconds. */
222 expiretime *= 1000;
223 } else if (type == RDB_OPCODE_EXPIRETIME_MS) {
224 /* EXPIRETIME_MS: milliseconds precision expire times introduced
225 * with RDB v3. Like EXPIRETIME but no with more precision. */
226 rdbstate.doing = RDB_CHECK_DOING_READ_EXPIRE;
227 if ((expiretime = rdbLoadMillisecondTime(&rdb)) == -1) goto eoferr;
228 /* We read the time so we need to read the object type again. */
229 rdbstate.doing = RDB_CHECK_DOING_READ_TYPE;
230 if ((type = rdbLoadType(&rdb)) == -1) goto eoferr;
231 } else if (type == RDB_OPCODE_EOF) {
232 /* EOF: End of file, exit the main loop. */
233 break;
234 } else if (type == RDB_OPCODE_SELECTDB) {
235 /* SELECTDB: Select the specified database. */
236 rdbstate.doing = RDB_CHECK_DOING_READ_LEN;
237 if ((dbid = rdbLoadLen(&rdb,NULL)) == RDB_LENERR)
238 goto eoferr;
239 rdbCheckInfo("Selecting DB ID %d", dbid);
240 continue; /* Read type again. */
241 } else if (type == RDB_OPCODE_RESIZEDB) {
242 /* RESIZEDB: Hint about the size of the keys in the currently
243 * selected data base, in order to avoid useless rehashing. */
244 uint64_t db_size, expires_size;
245 rdbstate.doing = RDB_CHECK_DOING_READ_LEN;
246 if ((db_size = rdbLoadLen(&rdb,NULL)) == RDB_LENERR)
247 goto eoferr;
248 if ((expires_size = rdbLoadLen(&rdb,NULL)) == RDB_LENERR)
249 goto eoferr;
250 continue; /* Read type again. */
251 } else if (type == RDB_OPCODE_AUX) {
252 /* AUX: generic string-string fields. Use to add state to RDB
253 * which is backward compatible. Implementations of RDB loading
254 * are requierd to skip AUX fields they don't understand.
255 *
256 * An AUX field is composed of two strings: key and value. */
257 robj *auxkey, *auxval;
258 rdbstate.doing = RDB_CHECK_DOING_READ_AUX;
259 if ((auxkey = rdbLoadStringObject(&rdb)) == NULL) goto eoferr;
260 if ((auxval = rdbLoadStringObject(&rdb)) == NULL) goto eoferr;
261
262 rdbCheckInfo("AUX FIELD %s = '%s'",
263 (char*)auxkey->ptr, (char*)auxval->ptr);
264 decrRefCount(auxkey);
265 decrRefCount(auxval);
266 continue; /* Read type again. */
267 } else {
268 if (!rdbIsObjectType(type)) {
269 rdbCheckError("Invalid object type: %d", type);
270 return 1;
271 }
272 rdbstate.key_type = type;
273 }
274
275 /* Read key */
276 rdbstate.doing = RDB_CHECK_DOING_READ_KEY;
277 if ((key = rdbLoadStringObject(&rdb)) == NULL) goto eoferr;
278 rdbstate.key = key;
279 rdbstate.keys++;
280 /* Read value */
281 rdbstate.doing = RDB_CHECK_DOING_READ_OBJECT_VALUE;
282 if ((val = rdbLoadObject(type,&rdb)) == NULL) goto eoferr;
283 /* Check if the key already expired. This function is used when loading
284 * an RDB file from disk, either at startup, or when an RDB was
285 * received from the master. In the latter case, the master is
286 * responsible for key expiry. If we would expire keys here, the
287 * snapshot taken by the master may not be reflected on the slave. */
288 if (server.masterhost == NULL && expiretime != -1 && expiretime < now)
289 rdbstate.already_expired++;
290 if (expiretime != -1) rdbstate.expires++;
291 rdbstate.key = NULL;
292 decrRefCount(key);
293 decrRefCount(val);
294 rdbstate.key_type = -1;
295 }
296 /* Verify the checksum if RDB version is >= 5 */
297 if (rdbver >= 5 && server.rdb_checksum) {
298 uint64_t cksum, expected = rdb.cksum;
299
300 rdbstate.doing = RDB_CHECK_DOING_CHECK_SUM;
301 if (rioRead(&rdb,&cksum,8) == 0) goto eoferr;
302 memrev64ifbe(&cksum);
303 if (cksum == 0) {
304 rdbCheckInfo("RDB file was saved with checksum disabled: no check performed.");
305 } else if (cksum != expected) {
306 rdbCheckError("RDB CRC error");
307 } else {
308 rdbCheckInfo("Checksum OK");
309 }
310 }
311
312 fclose(fp);
313 return 0;
314
315 eoferr: /* unexpected end of file is handled here with a fatal exit */
316 if (rdbstate.error_set) {
317 rdbCheckError(rdbstate.error);
318 } else {
319 rdbCheckError("Unexpected EOF reading RDB file");
320 }
321 return 1;
322 }
323
324 /* RDB check main: called form redis.c when Redis is executed with the
325 * redis-check-rdb alias.
326 *
327 * The function never returns, but exits with the status code according
328 * to success (RDB is sane) or error (RDB is corrupted). */
redis_check_rdb_main(int argc,char ** argv)329 int redis_check_rdb_main(int argc, char **argv) {
330 if (argc != 2) {
331 fprintf(stderr, "Usage: %s <rdb-file-name>\n", argv[0]);
332 exit(1);
333 }
334 createSharedObjects(); /* Needed for loading. */
335 server.loading_process_events_interval_bytes = 0;
336 rdbCheckMode = 1;
337 rdbCheckInfo("Checking RDB file %s", argv[1]);
338 rdbCheckSetupSignals();
339 int retval = redis_check_rdb(argv[1]);
340 if (retval == 0) {
341 rdbCheckInfo("\\o/ RDB looks OK! \\o/");
342 rdbShowGenericInfo();
343 }
344 exit(retval);
345 }
346