// 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},
{"quality", required_argument, nullptr, 0},
{nullptr, 0, nullptr, 0},
};
const uint32_t kBrotliDefaultQuality = BROTLI_MAX_QUALITY;
} // namespace
namespace bsdiff {
bool BsdiffArguments::IsValid() const {
if (compressor_type_ == CompressorType::kBrotli &&
(compression_quality_ < BROTLI_MIN_QUALITY ||
compression_quality_ > BROTLI_MAX_QUALITY)) {
return false;
}
if (format_ == BsdiffFormat::kLegacy) {
return compressor_type_ == CompressorType::kBZ2;
} else if (format_ == BsdiffFormat::kBsdf2) {
return (compressor_type_ == CompressorType::kBZ2 ||
compressor_type_ == CompressorType::kBrotli);
} else if (format_ == BsdiffFormat::kEndsley) {
// All compression options are valid for this format.
return true;
}
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 (!ParseCompressorType(optarg, &compressor_type_)) {
return false;
}
} else if (name == "quality") {
if (!ParseQuality(optarg, &compression_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 &&
compressor_type_ == CompressorType::kBrotli &&
compression_quality_ == -1) {
compression_quality_ = kBrotliDefaultQuality;
} else if (compressor_type_ != CompressorType::kBrotli &&
compression_quality_ != -1) {
std::cerr << "Warning: Compression quality is only used in the brotli"
" compressor." << endl;
}
return true;
}
bool BsdiffArguments::ParseCompressorType(const string& str,
CompressorType* type) {
string type_string = str;
std::transform(type_string.begin(), type_string.end(), type_string.begin(),
::tolower);
if (type_string == kNoCompressionString) {
*type = CompressorType::kNoCompression;
return true;
} else if (type_string == kBZ2String) {
*type = CompressorType::kBZ2;
return true;
} else if (type_string == kBrotliString) {
*type = CompressorType::kBrotli;
return true;
}
std::cerr << "Failed to parse compressor type in " << str << endl;
return false;
}
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) {
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 < BROTLI_MIN_QUALITY || result > BROTLI_MAX_QUALITY) {
std::cerr << "Compression quality out of range " << str << endl;
return false;
}
*quality = result;
return true;
}
} // namespace bsdiff