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 = 0; 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