You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
217 lines
6.1 KiB
217 lines
6.1 KiB
4 months ago
|
// 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
|