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 #include "server.h"
32 #include <sys/stat.h>
33
34 #define ERROR(...) { \
35 char __buf[1024]; \
36 snprintf(__buf, sizeof(__buf), __VA_ARGS__); \
37 snprintf(error, sizeof(error), "0x%16llx: %s", (long long)epos, __buf); \
38 }
39
40 static char error[1044];
41 static off_t epos;
42 static long long line = 1;
43 static time_t to_timestamp = 0;
44
consumeNewline(char * buf)45 int consumeNewline(char *buf) {
46 if (strncmp(buf,"\r\n",2) != 0) {
47 ERROR("Expected \\r\\n, got: %02x%02x",buf[0],buf[1]);
48 return 0;
49 }
50 line += 1;
51 return 1;
52 }
53
readAnnotations(FILE * fp)54 int readAnnotations(FILE *fp) {
55 char buf[AOF_ANNOTATION_LINE_MAX_LEN];
56 while (1) {
57 epos = ftello(fp);
58 if (fgets(buf, sizeof(buf), fp) == NULL) {
59 return 0;
60 }
61 if (buf[0] == '#') {
62 if (to_timestamp && strncmp(buf, "#TS:", 4) == 0) {
63 time_t ts = strtol(buf+4, NULL, 10);
64 if (ts <= to_timestamp) continue;
65 if (epos == 0) {
66 printf("AOF has nothing before timestamp %ld, "
67 "aborting...\n", to_timestamp);
68 fclose(fp);
69 exit(1);
70 }
71 /* Truncate remaining AOF if exceeding 'to_timestamp' */
72 if (ftruncate(fileno(fp), epos) == -1) {
73 printf("Failed to truncate AOF to timestamp %ld\n",
74 to_timestamp);
75 exit(1);
76 } else {
77 printf("Successfully truncated AOF to timestamp %ld\n",
78 to_timestamp);
79 fclose(fp);
80 exit(0);
81 }
82 }
83 continue;
84 } else {
85 if (fseek(fp, -(ftello(fp)-epos), SEEK_CUR) == -1) {
86 ERROR("Fseek error: %s", strerror(errno));
87 return 0;
88 }
89 return 1;
90 }
91 }
92 return 1;
93 }
94
readLong(FILE * fp,char prefix,long * target)95 int readLong(FILE *fp, char prefix, long *target) {
96 char buf[128], *eptr;
97 epos = ftello(fp);
98 if (fgets(buf,sizeof(buf),fp) == NULL) {
99 return 0;
100 }
101 if (buf[0] != prefix) {
102 ERROR("Expected prefix '%c', got: '%c'",prefix,buf[0]);
103 return 0;
104 }
105 *target = strtol(buf+1,&eptr,10);
106 return consumeNewline(eptr);
107 }
108
readBytes(FILE * fp,char * target,long length)109 int readBytes(FILE *fp, char *target, long length) {
110 long real;
111 epos = ftello(fp);
112 real = fread(target,1,length,fp);
113 if (real != length) {
114 ERROR("Expected to read %ld bytes, got %ld bytes",length,real);
115 return 0;
116 }
117 return 1;
118 }
119
readString(FILE * fp,char ** target)120 int readString(FILE *fp, char** target) {
121 long len;
122 *target = NULL;
123 if (!readLong(fp,'$',&len)) {
124 return 0;
125 }
126
127 if (len < 0 || len > LONG_MAX - 2) {
128 ERROR("Expected to read string of %ld bytes, which is not in the suitable range",len);
129 return 0;
130 }
131
132 /* Increase length to also consume \r\n */
133 len += 2;
134 *target = (char*)zmalloc(len);
135 if (!readBytes(fp,*target,len)) {
136 return 0;
137 }
138 if (!consumeNewline(*target+len-2)) {
139 return 0;
140 }
141 (*target)[len-2] = '\0';
142 return 1;
143 }
144
readArgc(FILE * fp,long * target)145 int readArgc(FILE *fp, long *target) {
146 return readLong(fp,'*',target);
147 }
148
process(FILE * fp)149 off_t process(FILE *fp) {
150 long argc;
151 off_t pos = 0;
152 int i, multi = 0;
153 char *str;
154
155 while(1) {
156 if (!multi) pos = ftello(fp);
157 if (!readAnnotations(fp)) break;
158 if (!readArgc(fp, &argc)) break;
159
160 for (i = 0; i < argc; i++) {
161 if (!readString(fp,&str)) break;
162 if (i == 0) {
163 if (strcasecmp(str, "multi") == 0) {
164 if (multi++) {
165 ERROR("Unexpected MULTI");
166 break;
167 }
168 } else if (strcasecmp(str, "exec") == 0) {
169 if (--multi) {
170 ERROR("Unexpected EXEC");
171 break;
172 }
173 }
174 }
175 zfree(str);
176 }
177
178 /* Stop if the loop did not finish */
179 if (i < argc) {
180 if (str) zfree(str);
181 break;
182 }
183 }
184
185 if (feof(fp) && multi && strlen(error) == 0) {
186 ERROR("Reached EOF before reading EXEC for MULTI");
187 }
188 if (strlen(error) > 0) {
189 printf("%s\n", error);
190 }
191 return pos;
192 }
193
redis_check_aof_main(int argc,char ** argv)194 int redis_check_aof_main(int argc, char **argv) {
195 char *filename;
196 int fix = 0;
197
198 if (argc < 2) {
199 goto invalid_args;
200 } else if (argc == 2) {
201 filename = argv[1];
202 } else if (argc == 3) {
203 if (!strcmp(argv[1],"--fix")) {
204 filename = argv[2];
205 fix = 1;
206 } else {
207 goto invalid_args;
208 }
209 } else if (argc == 4) {
210 if (!strcmp(argv[1], "--truncate-to-timestamp")) {
211 to_timestamp = strtol(argv[2],NULL,10);
212 filename = argv[3];
213 } else {
214 goto invalid_args;
215 }
216 } else {
217 goto invalid_args;
218 }
219
220 FILE *fp = fopen(filename,"r+");
221 if (fp == NULL) {
222 printf("Cannot open file: %s\n", filename);
223 exit(1);
224 }
225
226 struct redis_stat sb;
227 if (redis_fstat(fileno(fp),&sb) == -1) {
228 printf("Cannot stat file: %s\n", filename);
229 exit(1);
230 }
231
232 off_t size = sb.st_size;
233 if (size == 0) {
234 printf("Empty file: %s\n", filename);
235 exit(1);
236 }
237
238 /* This AOF file may have an RDB preamble. Check this to start, and if this
239 * is the case, start processing the RDB part. */
240 if (size >= 8) { /* There must be at least room for the RDB header. */
241 char sig[5];
242 int has_preamble = fread(sig,sizeof(sig),1,fp) == 1 &&
243 memcmp(sig,"REDIS",sizeof(sig)) == 0;
244 rewind(fp);
245 if (has_preamble) {
246 printf("The AOF appears to start with an RDB preamble.\n"
247 "Checking the RDB preamble to start:\n");
248 if (redis_check_rdb_main(argc,argv,fp) == C_ERR) {
249 printf("RDB preamble of AOF file is not sane, aborting.\n");
250 exit(1);
251 } else {
252 printf("RDB preamble is OK, proceeding with AOF tail...\n");
253 }
254 }
255 }
256
257 off_t pos = process(fp);
258 off_t diff = size-pos;
259
260 /* In truncate-to-timestamp mode, just exit if there is nothing to truncate. */
261 if (diff == 0 && to_timestamp) {
262 printf("Truncate nothing in AOF to timestamp %ld\n", to_timestamp);
263 fclose(fp);
264 exit(0);
265 }
266
267 printf("AOF analyzed: size=%lld, ok_up_to=%lld, ok_up_to_line=%lld, diff=%lld\n",
268 (long long) size, (long long) pos, line, (long long) diff);
269 if (diff > 0) {
270 if (fix) {
271 char buf[2];
272 printf("This will shrink the AOF from %lld bytes, with %lld bytes, to %lld bytes\n",(long long)size,(long long)diff,(long long)pos);
273 printf("Continue? [y/N]: ");
274 if (fgets(buf,sizeof(buf),stdin) == NULL ||
275 strncasecmp(buf,"y",1) != 0) {
276 printf("Aborting...\n");
277 exit(1);
278 }
279 if (ftruncate(fileno(fp), pos) == -1) {
280 printf("Failed to truncate AOF\n");
281 exit(1);
282 } else {
283 printf("Successfully truncated AOF\n");
284 }
285 } else {
286 printf("AOF is not valid. "
287 "Use the --fix option to try fixing it.\n");
288 exit(1);
289 }
290 } else {
291 printf("AOF is valid\n");
292 }
293
294 fclose(fp);
295 exit(0);
296
297 invalid_args:
298 printf("Usage: %s [--fix|--truncate-to-timestamp $timestamp] <file.aof>\n",
299 argv[0]);
300 exit(1);
301 }
302