Home | History | Annotate | Download | only in bsdiff
      1 // Copyright 2017 The Chromium OS Authors. All rights reserved.
      2 // Use of this source code is governed by a BSD-style license that can be
      3 // found in the LICENSE file.
      4 
      5 #include "bsdiff/bsdiff_arguments.h"
      6 
      7 #include <getopt.h>
      8 
      9 #include <algorithm>
     10 #include <iostream>
     11 
     12 #include "brotli/encode.h"
     13 
     14 using std::endl;
     15 using std::string;
     16 
     17 namespace {
     18 
     19 // The name in string for the compression algorithms.
     20 constexpr char kNoCompressionString[] = "nocompression";
     21 constexpr char kBZ2String[] = "bz2";
     22 constexpr char kBrotliString[] = "brotli";
     23 
     24 // The name in string for the bsdiff format.
     25 constexpr char kLegacyString[] = "legacy";
     26 constexpr char kBsdf2String[] = "bsdf2";
     27 constexpr char kBsdiff40String[] = "bsdiff40";
     28 constexpr char kEndsleyString[] = "endsley";
     29 
     30 const struct option OPTIONS[] = {
     31     {"format", required_argument, nullptr, 0},
     32     {"minlen", required_argument, nullptr, 0},
     33     {"type", required_argument, nullptr, 0},
     34     {"brotli_quality", required_argument, nullptr, 0},
     35     {nullptr, 0, nullptr, 0},
     36 };
     37 
     38 const uint32_t kBrotliDefaultQuality = BROTLI_MAX_QUALITY;
     39 
     40 }  // namespace
     41 
     42 namespace bsdiff {
     43 
     44 std::vector<CompressorType> BsdiffArguments::compressor_types() const {
     45   return std::vector<CompressorType>(compressor_types_.begin(),
     46                                      compressor_types_.end());
     47 }
     48 
     49 bool BsdiffArguments::IsValid() const {
     50   if (compressor_types_.empty()) {
     51     return false;
     52   }
     53 
     54   if (IsCompressorSupported(CompressorType::kBrotli) &&
     55       (brotli_quality_ < BROTLI_MIN_QUALITY ||
     56        brotli_quality_ > BROTLI_MAX_QUALITY)) {
     57     return false;
     58   }
     59 
     60   if (format_ == BsdiffFormat::kLegacy) {
     61     return compressor_types_.size() == 1 &&
     62            IsCompressorSupported(CompressorType::kBZ2);
     63   } else if (format_ == BsdiffFormat::kBsdf2) {
     64     if (IsCompressorSupported(CompressorType::kNoCompression)) {
     65       std::cerr << "no compression is not supported in Bsdf2 format\n";
     66       return false;
     67     }
     68     return true;
     69   } else if (format_ == BsdiffFormat::kEndsley) {
     70     // Only one compressor is supported for this format.
     71     return compressor_types_.size() == 1;
     72   }
     73   return false;
     74 }
     75 
     76 bool BsdiffArguments::ParseCommandLine(int argc, char** argv) {
     77   int opt;
     78   int option_index;
     79   while ((opt = getopt_long(argc, argv, "", OPTIONS, &option_index)) != -1) {
     80     if (opt != 0) {
     81       return false;
     82     }
     83 
     84     string name = OPTIONS[option_index].name;
     85     if (name == "format") {
     86       if (!ParseBsdiffFormat(optarg, &format_)) {
     87         return false;
     88       }
     89     } else if (name == "minlen") {
     90       if (!ParseMinLength(optarg, &min_length_)) {
     91         return false;
     92       }
     93     } else if (name == "type") {
     94       if (!ParseCompressorTypes(optarg, &compressor_types_)) {
     95         return false;
     96       }
     97     } else if (name == "brotli_quality") {
     98       if (!ParseQuality(optarg, &brotli_quality_, BROTLI_MIN_QUALITY,
     99                         BROTLI_MAX_QUALITY)) {
    100         return false;
    101       }
    102     } else {
    103       std::cerr << "Unrecognized options: " << name << endl;
    104       return false;
    105     }
    106   }
    107 
    108   // If quality is uninitialized for brotli, set it to default value.
    109   if (format_ != BsdiffFormat::kLegacy &&
    110       IsCompressorSupported(CompressorType::kBrotli) && brotli_quality_ == -1) {
    111     brotli_quality_ = kBrotliDefaultQuality;
    112   } else if (!IsCompressorSupported(CompressorType::kBrotli) &&
    113              brotli_quality_ != -1) {
    114     std::cerr << "Warning: Brotli quality is only used in the brotli"
    115                  " compressor.\n";
    116   }
    117 
    118   return true;
    119 }
    120 
    121 bool BsdiffArguments::ParseCompressorTypes(const string& str,
    122                                            std::set<CompressorType>* types) {
    123   types->clear();
    124   // The expected types string is separated by ":", e.g. bz2:brotli
    125   std::vector<std::string> type_list;
    126   size_t base = 0;
    127   size_t found;
    128   while (true) {
    129     found = str.find(":", base);
    130     type_list.emplace_back(str, base, found - base);
    131 
    132     if (found == str.npos)
    133       break;
    134     base = found + 1;
    135   }
    136 
    137   for (auto& type : type_list) {
    138     std::transform(type.begin(), type.end(), type.begin(), ::tolower);
    139     if (type == kNoCompressionString) {
    140       types->emplace(CompressorType::kNoCompression);
    141     } else if (type == kBZ2String) {
    142       types->emplace(CompressorType::kBZ2);
    143     } else if (type == kBrotliString) {
    144       types->emplace(CompressorType::kBrotli);
    145     } else {
    146       std::cerr << "Failed to parse compressor type in " << str << endl;
    147       return false;
    148     }
    149   }
    150 
    151   return true;
    152 }
    153 
    154 bool BsdiffArguments::ParseMinLength(const string& str, size_t* len) {
    155   errno = 0;
    156   char* end;
    157   const char* s = str.c_str();
    158   long result = strtol(s, &end, 10);
    159   if (errno != 0 || s == end || *end != '\0') {
    160     return false;
    161   }
    162 
    163   if (result < 0) {
    164     std::cerr << "Minimum length must be non-negative: " << str << endl;
    165     return false;
    166   }
    167 
    168   *len = result;
    169   return true;
    170 }
    171 
    172 bool BsdiffArguments::ParseBsdiffFormat(const string& str,
    173                                         BsdiffFormat* format) {
    174   string format_string = str;
    175   std::transform(format_string.begin(), format_string.end(),
    176                  format_string.begin(), ::tolower);
    177   if (format_string == kLegacyString || format_string == kBsdiff40String) {
    178     *format = BsdiffFormat::kLegacy;
    179     return true;
    180   } else if (format_string == kBsdf2String) {
    181     *format = BsdiffFormat::kBsdf2;
    182     return true;
    183   } else if (format_string == kEndsleyString) {
    184     *format = BsdiffFormat::kEndsley;
    185     return true;
    186   }
    187   std::cerr << "Failed to parse bsdiff format in " << str << endl;
    188   return false;
    189 }
    190 
    191 bool BsdiffArguments::ParseQuality(const string& str,
    192                                    int* quality,
    193                                    int min,
    194                                    int max) {
    195   errno = 0;
    196   char* end;
    197   const char* s = str.c_str();
    198   long result = strtol(s, &end, 10);
    199   if (errno != 0 || s == end || *end != '\0') {
    200     return false;
    201   }
    202 
    203   if (result < min || result > max) {
    204     std::cerr << "Compression quality out of range " << str << endl;
    205     return false;
    206   }
    207 
    208   *quality = result;
    209   return true;
    210 }
    211 
    212 bool BsdiffArguments::IsCompressorSupported(CompressorType type) const {
    213   return compressor_types_.find(type) != compressor_types_.end();
    214 }
    215 
    216 }  // namespace bsdiff
    217