普通文本  |  217行  |  6.11 KB

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