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