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 
consumeNewline(char * buf)43 int consumeNewline(char *buf) {
44     if (strncmp(buf,"\r\n",2) != 0) {
45         ERROR("Expected \\r\\n, got: %02x%02x",buf[0],buf[1]);
46         return 0;
47     }
48     return 1;
49 }
50 
readLong(FILE * fp,char prefix,long * target)51 int readLong(FILE *fp, char prefix, long *target) {
52     char buf[128], *eptr;
53     epos = ftello(fp);
54     if (fgets(buf,sizeof(buf),fp) == NULL) {
55         return 0;
56     }
57     if (buf[0] != prefix) {
58         ERROR("Expected prefix '%c', got: '%c'",prefix,buf[0]);
59         return 0;
60     }
61     *target = strtol(buf+1,&eptr,10);
62     return consumeNewline(eptr);
63 }
64 
readBytes(FILE * fp,char * target,long length)65 int readBytes(FILE *fp, char *target, long length) {
66     long real;
67     epos = ftello(fp);
68     real = fread(target,1,length,fp);
69     if (real != length) {
70         ERROR("Expected to read %ld bytes, got %ld bytes",length,real);
71         return 0;
72     }
73     return 1;
74 }
75 
readString(FILE * fp,char ** target)76 int readString(FILE *fp, char** target) {
77     long len;
78     *target = NULL;
79     if (!readLong(fp,'$',&len)) {
80         return 0;
81     }
82 
83     /* Increase length to also consume \r\n */
84     len += 2;
85     *target = (char*)zmalloc(len);
86     if (!readBytes(fp,*target,len)) {
87         return 0;
88     }
89     if (!consumeNewline(*target+len-2)) {
90         return 0;
91     }
92     (*target)[len-2] = '\0';
93     return 1;
94 }
95 
readArgc(FILE * fp,long * target)96 int readArgc(FILE *fp, long *target) {
97     return readLong(fp,'*',target);
98 }
99 
process(FILE * fp)100 off_t process(FILE *fp) {
101     long argc;
102     off_t pos = 0;
103     int i, multi = 0;
104     char *str;
105 
106     while(1) {
107         if (!multi) pos = ftello(fp);
108         if (!readArgc(fp, &argc)) break;
109 
110         for (i = 0; i < argc; i++) {
111             if (!readString(fp,&str)) break;
112             if (i == 0) {
113                 if (strcasecmp(str, "multi") == 0) {
114                     if (multi++) {
115                         ERROR("Unexpected MULTI");
116                         break;
117                     }
118                 } else if (strcasecmp(str, "exec") == 0) {
119                     if (--multi) {
120                         ERROR("Unexpected EXEC");
121                         break;
122                     }
123                 }
124             }
125             zfree(str);
126         }
127 
128         /* Stop if the loop did not finish */
129         if (i < argc) {
130             if (str) zfree(str);
131             break;
132         }
133     }
134 
135     if (feof(fp) && multi && strlen(error) == 0) {
136         ERROR("Reached EOF before reading EXEC for MULTI");
137     }
138     if (strlen(error) > 0) {
139         printf("%s\n", error);
140     }
141     return pos;
142 }
143 
redis_check_aof_main(int argc,char ** argv)144 int redis_check_aof_main(int argc, char **argv) {
145     char *filename;
146     int fix = 0;
147 
148     if (argc < 2) {
149         printf("Usage: %s [--fix] <file.aof>\n", argv[0]);
150         exit(1);
151     } else if (argc == 2) {
152         filename = argv[1];
153     } else if (argc == 3) {
154         if (strcmp(argv[1],"--fix") != 0) {
155             printf("Invalid argument: %s\n", argv[1]);
156             exit(1);
157         }
158         filename = argv[2];
159         fix = 1;
160     } else {
161         printf("Invalid arguments\n");
162         exit(1);
163     }
164 
165     FILE *fp = fopen(filename,"r+");
166     if (fp == NULL) {
167         printf("Cannot open file: %s\n", filename);
168         exit(1);
169     }
170 
171     struct redis_stat sb;
172     if (redis_fstat(fileno(fp),&sb) == -1) {
173         printf("Cannot stat file: %s\n", filename);
174         exit(1);
175     }
176 
177     off_t size = sb.st_size;
178     if (size == 0) {
179         printf("Empty file: %s\n", filename);
180         exit(1);
181     }
182 
183     /* This AOF file may have an RDB preamble. Check this to start, and if this
184      * is the case, start processing the RDB part. */
185     if (size >= 8) {    /* There must be at least room for the RDB header. */
186         char sig[5];
187         int has_preamble = fread(sig,sizeof(sig),1,fp) == 1 &&
188                             memcmp(sig,"REDIS",sizeof(sig)) == 0;
189         rewind(fp);
190         if (has_preamble) {
191             printf("The AOF appears to start with an RDB preamble.\n"
192                    "Checking the RDB preamble to start:\n");
193             if (redis_check_rdb_main(argc,argv,fp) == C_ERR) {
194                 printf("RDB preamble of AOF file is not sane, aborting.\n");
195                 exit(1);
196             } else {
197                 printf("RDB preamble is OK, proceeding with AOF tail...\n");
198             }
199         }
200     }
201 
202     off_t pos = process(fp);
203     off_t diff = size-pos;
204     printf("AOF analyzed: size=%lld, ok_up_to=%lld, diff=%lld\n",
205         (long long) size, (long long) pos, (long long) diff);
206     if (diff > 0) {
207         if (fix) {
208             char buf[2];
209             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);
210             printf("Continue? [y/N]: ");
211             if (fgets(buf,sizeof(buf),stdin) == NULL ||
212                 strncasecmp(buf,"y",1) != 0) {
213                     printf("Aborting...\n");
214                     exit(1);
215             }
216             if (ftruncate(fileno(fp), pos) == -1) {
217                 printf("Failed to truncate AOF\n");
218                 exit(1);
219             } else {
220                 printf("Successfully truncated AOF\n");
221             }
222         } else {
223             printf("AOF is not valid. "
224                    "Use the --fix option to try fixing it.\n");
225             exit(1);
226         }
227     } else {
228         printf("AOF is valid\n");
229     }
230 
231     fclose(fp);
232     exit(0);
233 }
234