// 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; 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