text: /*
   * Copyright 2018 Jonathan Dieter <jdieter@gmail.com>
   *
   * Redistribution and use in source and binary forms, with or without
   * modification, are permitted provided that the following conditions are met:
   *
   *  1. Redistributions of source code must retain the above copyright notice,
   *     this list of conditions and the following disclaimer.
   *
   *  2. 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.
   *
   * 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 HOLDER 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.
   */
  
  #include <stdlib.h>
  #include <stdio.h>
  #include <string.h>
  #include <stdint.h>
  #include <stdbool.h>
  #include <sys/types.h>
  #include <sys/stat.h>
  #include <fcntl.h>
  #include <unistd.h>
  #include <argp.h>
  #include <zck.h>
  
  #include "util_common.h"
  
  static char doc[] = "zck_delta_size - Calculate the difference between"
                      " two zchunk files";
  
  static char args_doc[] = "<file 1> <file 2>";
  
  static struct argp_option options[] = {
      {"verbose", 'v', 0,        0,
       "Increase verbosity (can be specified more than once for debugging)"},
      {"version", 'V', 0,        0, "Show program version"},
      { 0 }
  };
  
  struct arguments {
    char *args[2];
    zck_log_type log_level;
    bool exit;
  };
  
  static error_t parse_opt (int key, char *arg, struct argp_state *state) {
      struct arguments *arguments = state->input;
  
      if(arguments->exit)
          return 0;
  
      switch (key) {
          case 'v':
              arguments->log_level--;
              if(arguments->log_level < ZCK_LOG_DDEBUG)
                  arguments->log_level = ZCK_LOG_DDEBUG;
              break;
          case 'V':
              version();
              arguments->exit = true;
              break;
  
          case ARGP_KEY_ARG:
              if (state->arg_num >= 2) {
                  argp_usage (state);
                  return EINVAL;
              }
              arguments->args[state->arg_num] = arg;
  
              break;
  
          case ARGP_KEY_END:
              if (state->arg_num < 2) {
                  argp_usage (state);
                  return EINVAL;
              }
              break;
  
          default:
              return ARGP_ERR_UNKNOWN;
      }
      return 0;
  }
  
  static struct argp argp = {options, parse_opt, args_doc, doc};
  
  int main (int argc, char *argv[]) {
      struct arguments arguments = {0};
  
      /* Defaults */
      arguments.log_level = ZCK_LOG_ERROR;
  
      int retval = argp_parse(&argp, argc, argv, 0, 0, &arguments);
      if(retval || arguments.exit)
          exit(retval);
  
      zck_set_log_level(arguments.log_level);
  
      int src_fd = open(arguments.args[0], O_RDONLY | O_BINARY);
      if(src_fd < 0) {
          LOG_ERROR("Unable to open %s\n", arguments.args[0]);
          perror("");
          exit(1);
      }
      zckCtx *zck_src = zck_create();
      if(zck_src == NULL) {
          LOG_ERROR("%s", zck_get_error(NULL));
          zck_clear_error(NULL);
          exit(1);
      }
      if(!zck_init_read(zck_src, src_fd)) {
          LOG_ERROR("Error reading %s: %s", arguments.args[0],
                  zck_get_error(zck_src));
          zck_free(&zck_src);
          exit(1);
      }
      close(src_fd);
  
      int tgt_fd = open(arguments.args[1], O_RDONLY | O_BINARY);
      if(tgt_fd < 0) {
          LOG_ERROR("Unable to open %s\n", arguments.args[1]);
          perror("");
          zck_free(&zck_src);
          exit(1);
      }
      zckCtx *zck_tgt = zck_create();
      if(zck_tgt == NULL) {
          LOG_ERROR("%s", zck_get_error(NULL));
          zck_clear_error(NULL);
          zck_free(&zck_src);
          exit(1);
      }
      if(!zck_init_read(zck_tgt, tgt_fd)) {
          LOG_ERROR("Error reading %s: %s", arguments.args[1],
                  zck_get_error(zck_tgt));
          zck_free(&zck_src);
          zck_free(&zck_tgt);
          exit(1);
      }
      close(tgt_fd);
  
      if(zck_get_chunk_hash_type(zck_tgt) != zck_get_chunk_hash_type(zck_src)) {
          LOG_ERROR("ERROR: Chunk hash types don't match:\n");
          LOG_ERROR("   %s: %s\n", arguments.args[0],
                  zck_hash_name_from_type(zck_get_chunk_hash_type(zck_tgt)));
          LOG_ERROR("   %s: %s\n", arguments.args[1],
                  zck_hash_name_from_type(zck_get_chunk_hash_type(zck_src)));
          zck_free(&zck_src);
          zck_free(&zck_tgt);
          exit(1);
      }
      zckChunk *tgt_idx = zck_get_first_chunk(zck_tgt);
      zckChunk *src_idx = zck_get_first_chunk(zck_src);
      if(tgt_idx == NULL || src_idx == NULL)
          exit(1);
  
      if(!zck_compare_chunk_digest(tgt_idx, src_idx))
          LOG_ERROR("WARNING: Dicts don't match\n");
      ssize_t dl_size = zck_get_header_length(zck_tgt);
      if(dl_size < 0)
          exit(1);
      ssize_t total_size = zck_get_header_length(zck_tgt);
      ssize_t matched_chunks = 0;
      for(tgt_idx = zck_get_first_chunk(zck_tgt); tgt_idx;
          tgt_idx = zck_get_next_chunk(tgt_idx)) {
          bool found = false;
          for(src_idx = zck_get_first_chunk(zck_src); src_idx;
              src_idx = zck_get_next_chunk(src_idx)) {
              if(zck_compare_chunk_digest(tgt_idx, src_idx)) {
                  found = true;
                  break;
              }
          }
          if(!found) {
              dl_size += zck_get_chunk_comp_size(tgt_idx);
          } else {
              matched_chunks += 1;
          }
          total_size += zck_get_chunk_comp_size(tgt_idx);
      }
      printf("Would download %lli of %lli bytes\n", (long long) dl_size,
             (long long) total_size);
      printf("Matched %lli of %llu chunks\n", (long long) matched_chunks,
             (long long unsigned) zck_get_chunk_count(zck_tgt));
      zck_free(&zck_tgt);
      zck_free(&zck_src);
  }
  