text: /*
   * Copyright (c) 2009-2012, Pieter Noordhuis <pcnoordhuis at gmail dot com>
   * Copyright (c) 2009-2012, Salvatore Sanfilippo <antirez at gmail dot com>
   * All rights reserved.
   *
   * Redistribution and use in source and binary forms, with or without
   * modification, are permitted provided that the following conditions are met:
   *
   *   * Redistributions of source code must retain the above copyright notice,
   *     this list of conditions and the following disclaimer.
   *   * Redistributions in binary form must reproduce the above copyright
   *     notice, this list of conditions and the following disclaimer in the
   *     documentation and/or other materials provided with the distribution.
   *   * Neither the name of Redis nor the names of its contributors may be used
   *     to endorse or promote products derived from this software without
   *     specific prior written permission.
   *
   * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
   * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
   * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
   * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
   * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
   * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
   * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
   * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
   * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
   * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
   * POSSIBILITY OF SUCH DAMAGE.
   */
  
  #ifdef _WIN32
  #include "Win32_Interop/win32fixes.h"
  #include "Win32_Interop/Win32_Signal_Process.h"
  #include "Win32_Interop/Win32_ANSI.h"
  #endif
  
  #include "server.h"
  #include <sys/stat.h>
  
  #define ERROR(...) { \
      char __buf[1024]; \
      snprintf(__buf, sizeof(__buf), __VA_ARGS__); \
      snprintf(error, sizeof(error), "0x%16llx: %s", (long long)epos, __buf); \
  }
  
  static char error[1024];
  static off_t epos;
  
  int consumeNewline(char *buf) {
      if (strncmp(buf,"\r\n",2) != 0) {
          ERROR("Expected \\r\\n, got: %02x%02x",buf[0],buf[1]);
          return 0;
      }
      return 1;
  }
  
  int readLong(FILE *fp, char prefix, long *target) {
      char buf[128], *eptr;
      epos = ftello(fp);
      if (fgets(buf,sizeof(buf),fp) == NULL) {
          return 0;
      }
      if (buf[0] != prefix) {
          ERROR("Expected prefix '%c', got: '%c'",prefix,buf[0]);
          return 0;
      }
      *target = strtol(buf+1,&eptr,10);
      return consumeNewline(eptr);
  }
  
  int readBytes(FILE *fp, char *target, long length) {
      long real;
      epos = ftello(fp);
      real = fread(target,1,length,fp);
      if (real != length) {
          ERROR("Expected to read %ld bytes, got %ld bytes",length,real);
          return 0;
      }
      return 1;
  }
  
  int readString(FILE *fp, char** target) {
      long len;
      *target = NULL;
      if (!readLong(fp,'$',&len)) {
          return 0;
      }
  
      /* Increase length to also consume \r\n */
      len += 2;
      *target = (char*)zmalloc(len);
      if (!readBytes(fp,*target,len)) {
          return 0;
      }
      if (!consumeNewline(*target+len-2)) {
          return 0;
      }
      (*target)[len-2] = '\0';
      return 1;
  }
  
  int readArgc(FILE *fp, long *target) {
      return readLong(fp,'*',target);
  }
  
  off_t process(FILE *fp) {
      long argc;
      off_t pos = 0;
      int i, multi = 0;
      char *str;
  
      while(1) {
          if (!multi) pos = ftello(fp);
          if (!readArgc(fp, &argc)) break;
  
          for (i = 0; i < argc; i++) {
              if (!readString(fp,&str)) break;
              if (i == 0) {
                  if (strcasecmp(str, "multi") == 0) {
                      if (multi++) {
                          ERROR("Unexpected MULTI");
                          break;
                      }
                  } else if (strcasecmp(str, "exec") == 0) {
                      if (--multi) {
                          ERROR("Unexpected EXEC");
                          break;
                      }
                  }
              }
              zfree(str);
          }
  
          /* Stop if the loop did not finish */
          if (i < argc) {
              if (str) zfree(str);
              break;
          }
      }
  
      if (feof(fp) && multi && strlen(error) == 0) {
          ERROR("Reached EOF before reading EXEC for MULTI");
      }
      if (strlen(error) > 0) {
          printf("%s\n", error);
      }
      return pos;
  }
  
  int redis_check_aof_main(int argc, char **argv) {
      char *filename;
      int fix = 0;
  
      if (argc < 2) {
          printf("Usage: %s [--fix] <file.aof>\n", argv[0]);
          exit(1);
      } else if (argc == 2) {
          filename = argv[1];
      } else if (argc == 3) {
          if (strcmp(argv[1],"--fix") != 0) {
              printf("Invalid argument: %s\n", argv[1]);
              exit(1);
          }
          filename = argv[2];
          fix = 1;
      } else {
          printf("Invalid arguments\n");
          exit(1);
      }
  
      FILE *fp = fopen(filename,"r+");
      if (fp == NULL) {
          printf("Cannot open file: %s\n", filename);
          exit(1);
      }
  
      struct redis_stat sb;
      if (redis_fstat(fileno(fp),&sb) == -1) {
          printf("Cannot stat file: %s\n", filename);
          exit(1);
      }
  
      off_t size = sb.st_size;
      if (size == 0) {
          printf("Empty file: %s\n", filename);
          exit(1);
      }
  
      /* This AOF file may have an RDB preamble. Check this to start, and if this
       * is the case, start processing the RDB part. */
      if (size >= 8) {    /* There must be at least room for the RDB header. */
          char sig[5];
          int has_preamble = fread(sig,sizeof(sig),1,fp) == 1 &&
                              memcmp(sig,"REDIS",sizeof(sig)) == 0;
          rewind(fp);
          if (has_preamble) {
              printf("The AOF appears to start with an RDB preamble.\n"
                     "Checking the RDB preamble to start:\n");
              if (redis_check_rdb_main(argc,argv,fp) == C_ERR) {
                  printf("RDB preamble of AOF file is not sane, aborting.\n");
                  exit(1);
              } else {
                  printf("RDB preamble is OK, proceeding with AOF tail...\n");
              }
          }
      }
  
      off_t pos = process(fp);
      off_t diff = size-pos;
      printf("AOF analyzed: size=%lld, ok_up_to=%lld, diff=%lld\n",
          (long long) size, (long long) pos, (long long) diff);
      if (diff > 0) {
          if (fix) {
              char buf[2];
              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);
              printf("Continue? [y/N]: ");
              if (fgets(buf,sizeof(buf),stdin) == NULL ||
                  strncasecmp(buf,"y",1) != 0) {
                      printf("Aborting...\n");
                      exit(1);
              }
              if (ftruncate(fileno(fp), pos) == -1) {
                  printf("Failed to truncate AOF\n");
                  exit(1);
              } else {
                  printf("Successfully truncated AOF\n");
              }
          } else {
              printf("AOF is not valid. "
                     "Use the --fix option to try fixing it.\n");
              exit(1);
          }
      } else {
          printf("AOF is valid\n");
      }
  
      fclose(fp);
      exit(0);
  }
  