| // Copyright 2017 The Chromium OS Authors. All rights reserved. | |
| // Use of this source code is governed by a BSD-style license that can be | |
| // found in the LICENSE file. | |
| #include "bsdiff/bsdiff_arguments.h" | |
| #include <getopt.h> | |
| #include <algorithm> | |
| #include <iostream> | |
| #include "brotli/encode.h" | |
| using std::endl; | |
| using std::string; | |
| namespace { | |
| // The name in string for the compression algorithms. | |
| constexpr char kNoCompressionString[] = "nocompression"; | |
| constexpr char kBZ2String[] = "bz2"; | |
| constexpr char kBrotliString[] = "brotli"; | |
| // The name in string for the bsdiff format. | |
| constexpr char kLegacyString[] = "legacy"; | |
| constexpr char kBsdf2String[] = "bsdf2"; | |
| constexpr char kBsdiff40String[] = "bsdiff40"; | |
| constexpr char kEndsleyString[] = "endsley"; | |
| const struct option OPTIONS[] = { | |
| {"format", required_argument, nullptr, 0}, | |
| {"minlen", required_argument, nullptr, 0}, | |
| {"type", required_argument, nullptr, 0}, | |
| {"brotli_quality", required_argument, nullptr, 0}, | |
| {nullptr, 0, nullptr, 0}, | |
| }; | |
| const uint32_t kBrotliDefaultQuality = BROTLI_MAX_QUALITY; | |
| } // namespace | |
| namespace bsdiff { | |
| std::vector<CompressorType> BsdiffArguments::compressor_types() const { | |
| return std::vector<CompressorType>(compressor_types_.begin(), | |
| compressor_types_.end()); | |
| } | |
| bool BsdiffArguments::IsValid() const { | |
| if (compressor_types_.empty()) { | |
| return false; | |
| } | |
| if (IsCompressorSupported(CompressorType::kBrotli) && | |
| (brotli_quality_ < BROTLI_MIN_QUALITY || | |
| brotli_quality_ > BROTLI_MAX_QUALITY)) { | |
| return false; | |
| } | |
| if (format_ == BsdiffFormat::kLegacy) { | |
| return compressor_types_.size() == 1 && | |
| IsCompressorSupported(CompressorType::kBZ2); | |
| } else if (format_ == BsdiffFormat::kBsdf2) { | |
| if (IsCompressorSupported(CompressorType::kNoCompression)) { | |
| std::cerr << "no compression is not supported in Bsdf2 format\n"; | |
| return false; | |
| } | |
| return true; | |
| } else if (format_ == BsdiffFormat::kEndsley) { | |
| // Only one compressor is supported for this format. | |
| return compressor_types_.size() == 1; | |
| } | |
| return false; | |
| } | |
| bool BsdiffArguments::ParseCommandLine(int argc, char** argv) { | |
| int opt; | |
| int option_index = 0; | |
| while ((opt = getopt_long(argc, argv, "", OPTIONS, &option_index)) != -1) { | |
| if (opt != 0) { | |
| return false; | |
| } | |
| string name = OPTIONS[option_index].name; | |
| if (name == "format") { | |
| if (!ParseBsdiffFormat(optarg, &format_)) { | |
| return false; | |
| } | |
| } else if (name == "minlen") { | |
| if (!ParseMinLength(optarg, &min_length_)) { | |
| return false; | |
| } | |
| } else if (name == "type") { | |
| if (!ParseCompressorTypes(optarg, &compressor_types_)) { | |
| return false; | |
| } | |
| } else if (name == "brotli_quality") { | |
| if (!ParseQuality(optarg, &brotli_quality_, BROTLI_MIN_QUALITY, | |
| BROTLI_MAX_QUALITY)) { | |
| return false; | |
| } | |
| } else { | |
| std::cerr << "Unrecognized options: " << name << endl; | |
| return false; | |
| } | |
| } | |
| // If quality is uninitialized for brotli, set it to default value. | |
| if (format_ != BsdiffFormat::kLegacy && | |
| IsCompressorSupported(CompressorType::kBrotli) && brotli_quality_ == -1) { | |
| brotli_quality_ = kBrotliDefaultQuality; | |
| } else if (!IsCompressorSupported(CompressorType::kBrotli) && | |
| brotli_quality_ != -1) { | |
| std::cerr << "Warning: Brotli quality is only used in the brotli" | |
| " compressor.\n"; | |
| } | |
| return true; | |
| } | |
| bool BsdiffArguments::ParseCompressorTypes(const string& str, | |
| std::set<CompressorType>* types) { | |
| types->clear(); | |
| // The expected types string is separated by ":", e.g. bz2:brotli | |
| std::vector<std::string> type_list; | |
| size_t base = 0; | |
| size_t found; | |
| while (true) { | |
| found = str.find(":", base); | |
| type_list.emplace_back(str, base, found - base); | |
| if (found == str.npos) | |
| break; | |
| base = found + 1; | |
| } | |
| for (auto& type : type_list) { | |
| std::transform(type.begin(), type.end(), type.begin(), ::tolower); | |
| if (type == kNoCompressionString) { | |
| types->emplace(CompressorType::kNoCompression); | |
| } else if (type == kBZ2String) { | |
| types->emplace(CompressorType::kBZ2); | |
| } else if (type == kBrotliString) { | |
| types->emplace(CompressorType::kBrotli); | |
| } else { | |
| std::cerr << "Failed to parse compressor type in " << str << endl; | |
| return false; | |
| } | |
| } | |
| return true; | |
| } | |
| bool BsdiffArguments::ParseMinLength(const string& str, size_t* len) { | |
| errno = 0; | |
| char* end; | |
| const char* s = str.c_str(); | |
| long result = strtol(s, &end, 10); | |
| if (errno != 0 || s == end || *end != '\0') { | |
| return false; | |
| } | |
| if (result < 0) { | |
| std::cerr << "Minimum length must be non-negative: " << str << endl; | |
| return false; | |
| } | |
| *len = result; | |
| return true; | |
| } | |
| bool BsdiffArguments::ParseBsdiffFormat(const string& str, | |
| BsdiffFormat* format) { | |
| string format_string = str; | |
| std::transform(format_string.begin(), format_string.end(), | |
| format_string.begin(), ::tolower); | |
| if (format_string == kLegacyString || format_string == kBsdiff40String) { | |
| *format = BsdiffFormat::kLegacy; | |
| return true; | |
| } else if (format_string == kBsdf2String) { | |
| *format = BsdiffFormat::kBsdf2; | |
| return true; | |
| } else if (format_string == kEndsleyString) { | |
| *format = BsdiffFormat::kEndsley; | |
| return true; | |
| } | |
| std::cerr << "Failed to parse bsdiff format in " << str << endl; | |
| return false; | |
| } | |
| bool BsdiffArguments::ParseQuality(const string& str, | |
| int* quality, | |
| int min, | |
| int max) { | |
| errno = 0; | |
| char* end; | |
| const char* s = str.c_str(); | |
| long result = strtol(s, &end, 10); | |
| if (errno != 0 || s == end || *end != '\0') { | |
| return false; | |
| } | |
| if (result < min || result > max) { | |
| std::cerr << "Compression quality out of range " << str << endl; | |
| return false; | |
| } | |
| *quality = result; | |
| return true; | |
| } | |
| bool BsdiffArguments::IsCompressorSupported(CompressorType type) const { | |
| return compressor_types_.find(type) != compressor_types_.end(); | |
| } | |
| } // namespace bsdiff |