1 // Copyright 2017 The Chromium OS Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
4 
5 #include "bsdiff/bsdiff_arguments.h"
6 
7 #include <getopt.h>
8 
9 #include <algorithm>
10 #include <iostream>
11 
12 #include "brotli/encode.h"
13 
14 using std::endl;
15 using std::string;
16 
17 namespace {
18 
19 // The name in string for the compression algorithms.
20 constexpr char kNoCompressionString[] = "nocompression";
21 constexpr char kBZ2String[] = "bz2";
22 constexpr char kBrotliString[] = "brotli";
23 
24 // The name in string for the bsdiff format.
25 constexpr char kLegacyString[] = "legacy";
26 constexpr char kBsdf2String[] = "bsdf2";
27 constexpr char kBsdiff40String[] = "bsdiff40";
28 constexpr char kEndsleyString[] = "endsley";
29 
30 const struct option OPTIONS[] = {
31     {"format", required_argument, nullptr, 0},
32     {"minlen", required_argument, nullptr, 0},
33     {"type", required_argument, nullptr, 0},
34     {"brotli_quality", required_argument, nullptr, 0},
35     {nullptr, 0, nullptr, 0},
36 };
37 
38 const uint32_t kBrotliDefaultQuality = BROTLI_MAX_QUALITY;
39 
40 }  // namespace
41 
42 namespace bsdiff {
43 
44 std::vector<CompressorType> BsdiffArguments::compressor_types() const {
45   return std::vector<CompressorType>(compressor_types_.begin(),
46                                      compressor_types_.end());
47 }
48 
49 bool BsdiffArguments::IsValid() const {
50   if (compressor_types_.empty()) {
51     return false;
52   }
53 
54   if (IsCompressorSupported(CompressorType::kBrotli) &&
55       (brotli_quality_ < BROTLI_MIN_QUALITY ||
56        brotli_quality_ > BROTLI_MAX_QUALITY)) {
57     return false;
58   }
59 
60   if (format_ == BsdiffFormat::kLegacy) {
61     return compressor_types_.size() == 1 &&
62            IsCompressorSupported(CompressorType::kBZ2);
63   } else if (format_ == BsdiffFormat::kBsdf2) {
64     if (IsCompressorSupported(CompressorType::kNoCompression)) {
65       std::cerr << "no compression is not supported in Bsdf2 format\n";
66       return false;
67     }
68     return true;
69   } else if (format_ == BsdiffFormat::kEndsley) {
70     // Only one compressor is supported for this format.
71     return compressor_types_.size() == 1;
72   }
73   return false;
74 }
75 
76 bool BsdiffArguments::ParseCommandLine(int argc, char** argv) {
77   int opt;
78   int option_index = 0;
79   while ((opt = getopt_long(argc, argv, "", OPTIONS, &option_index)) != -1) {
80     if (opt != 0) {
81       return false;
82     }
83 
84     string name = OPTIONS[option_index].name;
85     if (name == "format") {
86       if (!ParseBsdiffFormat(optarg, &format_)) {
87         return false;
88       }
89     } else if (name == "minlen") {
90       if (!ParseMinLength(optarg, &min_length_)) {
91         return false;
92       }
93     } else if (name == "type") {
94       if (!ParseCompressorTypes(optarg, &compressor_types_)) {
95         return false;
96       }
97     } else if (name == "brotli_quality") {
98       if (!ParseQuality(optarg, &brotli_quality_, BROTLI_MIN_QUALITY,
99                         BROTLI_MAX_QUALITY)) {
100         return false;
101       }
102     } else {
103       std::cerr << "Unrecognized options: " << name << endl;
104       return false;
105     }
106   }
107 
108   // If quality is uninitialized for brotli, set it to default value.
109   if (format_ != BsdiffFormat::kLegacy &&
110       IsCompressorSupported(CompressorType::kBrotli) && brotli_quality_ == -1) {
111     brotli_quality_ = kBrotliDefaultQuality;
112   } else if (!IsCompressorSupported(CompressorType::kBrotli) &&
113              brotli_quality_ != -1) {
114     std::cerr << "Warning: Brotli quality is only used in the brotli"
115                  " compressor.\n";
116   }
117 
118   return true;
119 }
120 
121 bool BsdiffArguments::ParseCompressorTypes(const string& str,
122                                            std::set<CompressorType>* types) {
123   types->clear();
124   // The expected types string is separated by ":", e.g. bz2:brotli
125   std::vector<std::string> type_list;
126   size_t base = 0;
127   size_t found;
128   while (true) {
129     found = str.find(":", base);
130     type_list.emplace_back(str, base, found - base);
131 
132     if (found == str.npos)
133       break;
134     base = found + 1;
135   }
136 
137   for (auto& type : type_list) {
138     std::transform(type.begin(), type.end(), type.begin(), ::tolower);
139     if (type == kNoCompressionString) {
140       types->emplace(CompressorType::kNoCompression);
141     } else if (type == kBZ2String) {
142       types->emplace(CompressorType::kBZ2);
143     } else if (type == kBrotliString) {
144       types->emplace(CompressorType::kBrotli);
145     } else {
146       std::cerr << "Failed to parse compressor type in " << str << endl;
147       return false;
148     }
149   }
150 
151   return true;
152 }
153 
154 bool BsdiffArguments::ParseMinLength(const string& str, size_t* len) {
155   errno = 0;
156   char* end;
157   const char* s = str.c_str();
158   long result = strtol(s, &end, 10);
159   if (errno != 0 || s == end || *end != '\0') {
160     return false;
161   }
162 
163   if (result < 0) {
164     std::cerr << "Minimum length must be non-negative: " << str << endl;
165     return false;
166   }
167 
168   *len = result;
169   return true;
170 }
171 
172 bool BsdiffArguments::ParseBsdiffFormat(const string& str,
173                                         BsdiffFormat* format) {
174   string format_string = str;
175   std::transform(format_string.begin(), format_string.end(),
176                  format_string.begin(), ::tolower);
177   if (format_string == kLegacyString || format_string == kBsdiff40String) {
178     *format = BsdiffFormat::kLegacy;
179     return true;
180   } else if (format_string == kBsdf2String) {
181     *format = BsdiffFormat::kBsdf2;
182     return true;
183   } else if (format_string == kEndsleyString) {
184     *format = BsdiffFormat::kEndsley;
185     return true;
186   }
187   std::cerr << "Failed to parse bsdiff format in " << str << endl;
188   return false;
189 }
190 
191 bool BsdiffArguments::ParseQuality(const string& str,
192                                    int* quality,
193                                    int min,
194                                    int max) {
195   errno = 0;
196   char* end;
197   const char* s = str.c_str();
198   long result = strtol(s, &end, 10);
199   if (errno != 0 || s == end || *end != '\0') {
200     return false;
201   }
202 
203   if (result < min || result > max) {
204     std::cerr << "Compression quality out of range " << str << endl;
205     return false;
206   }
207 
208   *quality = result;
209   return true;
210 }
211 
212 bool BsdiffArguments::IsCompressorSupported(CompressorType type) const {
213   return compressor_types_.find(type) != compressor_types_.end();
214 }
215 
216 }  // namespace bsdiff
217