1*a03ca8b9SKrzysztof Kosiński // Copyright 2017 The Chromium Authors. All rights reserved.
2*a03ca8b9SKrzysztof Kosiński // Use of this source code is governed by a BSD-style license that can be
3*a03ca8b9SKrzysztof Kosiński // found in the LICENSE file.
4*a03ca8b9SKrzysztof Kosiński
5*a03ca8b9SKrzysztof Kosiński #include "components/zucchini/main_utils.h"
6*a03ca8b9SKrzysztof Kosiński
7*a03ca8b9SKrzysztof Kosiński #include <stddef.h>
8*a03ca8b9SKrzysztof Kosiński
9*a03ca8b9SKrzysztof Kosiński #include <memory>
10*a03ca8b9SKrzysztof Kosiński #include <ostream>
11*a03ca8b9SKrzysztof Kosiński #include <string>
12*a03ca8b9SKrzysztof Kosiński #include <vector>
13*a03ca8b9SKrzysztof Kosiński
14*a03ca8b9SKrzysztof Kosiński #include "base/command_line.h"
15*a03ca8b9SKrzysztof Kosiński #include "base/files/file_path.h"
16*a03ca8b9SKrzysztof Kosiński #include "base/files/file_util.h"
17*a03ca8b9SKrzysztof Kosiński #include "base/logging.h"
18*a03ca8b9SKrzysztof Kosiński #include "base/process/process_handle.h"
19*a03ca8b9SKrzysztof Kosiński #include "base/strings/string_number_conversions.h"
20*a03ca8b9SKrzysztof Kosiński #include "base/strings/string_split.h"
21*a03ca8b9SKrzysztof Kosiński #include "base/strings/string_util.h"
22*a03ca8b9SKrzysztof Kosiński #include "base/time/time.h"
23*a03ca8b9SKrzysztof Kosiński #include "build/build_config.h"
24*a03ca8b9SKrzysztof Kosiński #include "components/zucchini/io_utils.h"
25*a03ca8b9SKrzysztof Kosiński #include "components/zucchini/version_info.h"
26*a03ca8b9SKrzysztof Kosiński #include "components/zucchini/zucchini_commands.h"
27*a03ca8b9SKrzysztof Kosiński
28*a03ca8b9SKrzysztof Kosiński #if defined(OS_WIN)
29*a03ca8b9SKrzysztof Kosiński #include <windows.h> // This include must come first.
30*a03ca8b9SKrzysztof Kosiński
31*a03ca8b9SKrzysztof Kosiński #include <psapi.h>
32*a03ca8b9SKrzysztof Kosiński #endif
33*a03ca8b9SKrzysztof Kosiński
34*a03ca8b9SKrzysztof Kosiński namespace {
35*a03ca8b9SKrzysztof Kosiński
36*a03ca8b9SKrzysztof Kosiński /******** Command ********/
37*a03ca8b9SKrzysztof Kosiński
38*a03ca8b9SKrzysztof Kosiński // Specifications for a Zucchini command.
39*a03ca8b9SKrzysztof Kosiński struct Command {
Command__anonf9d0c39c0111::Command40*a03ca8b9SKrzysztof Kosiński constexpr Command(const char* name_in,
41*a03ca8b9SKrzysztof Kosiński const char* usage_in,
42*a03ca8b9SKrzysztof Kosiński int num_args_in,
43*a03ca8b9SKrzysztof Kosiński CommandFunction command_function_in)
44*a03ca8b9SKrzysztof Kosiński : name(name_in),
45*a03ca8b9SKrzysztof Kosiński usage(usage_in),
46*a03ca8b9SKrzysztof Kosiński num_args(num_args_in),
47*a03ca8b9SKrzysztof Kosiński command_function(command_function_in) {}
48*a03ca8b9SKrzysztof Kosiński Command(const Command&) = default;
49*a03ca8b9SKrzysztof Kosiński ~Command() = default;
50*a03ca8b9SKrzysztof Kosiński
51*a03ca8b9SKrzysztof Kosiński // Unique name of command. |-name| is used to select from command-line.
52*a03ca8b9SKrzysztof Kosiński const char* const name;
53*a03ca8b9SKrzysztof Kosiński
54*a03ca8b9SKrzysztof Kosiński // Usage help text of command.
55*a03ca8b9SKrzysztof Kosiński const char* const usage;
56*a03ca8b9SKrzysztof Kosiński
57*a03ca8b9SKrzysztof Kosiński // Number of arguments (assumed to be filenames) used by the command.
58*a03ca8b9SKrzysztof Kosiński const int num_args;
59*a03ca8b9SKrzysztof Kosiński
60*a03ca8b9SKrzysztof Kosiński // Main function to run for the command.
61*a03ca8b9SKrzysztof Kosiński const CommandFunction command_function;
62*a03ca8b9SKrzysztof Kosiński };
63*a03ca8b9SKrzysztof Kosiński
64*a03ca8b9SKrzysztof Kosiński /******** List of Zucchini commands ********/
65*a03ca8b9SKrzysztof Kosiński
66*a03ca8b9SKrzysztof Kosiński constexpr Command kCommands[] = {
67*a03ca8b9SKrzysztof Kosiński {"gen",
68*a03ca8b9SKrzysztof Kosiński "-gen <old_file> <new_file> <patch_file> [-raw] [-keep]"
69*a03ca8b9SKrzysztof Kosiński " [-impose=#+#=#+#,#+#=#+#,...]",
70*a03ca8b9SKrzysztof Kosiński 3, &MainGen},
71*a03ca8b9SKrzysztof Kosiński {"apply", "-apply <old_file> <patch_file> <new_file> [-keep]", 3,
72*a03ca8b9SKrzysztof Kosiński &MainApply},
73*a03ca8b9SKrzysztof Kosiński {"verify", "-verify <patch_file>", 1, &MainVerify},
74*a03ca8b9SKrzysztof Kosiński {"read", "-read <exe> [-dump]", 1, &MainRead},
75*a03ca8b9SKrzysztof Kosiński {"detect", "-detect <archive_file>", 1, &MainDetect},
76*a03ca8b9SKrzysztof Kosiński {"match", "-match <old_file> <new_file> [-impose=#+#=#+#,#+#=#+#,...]", 2,
77*a03ca8b9SKrzysztof Kosiński &MainMatch},
78*a03ca8b9SKrzysztof Kosiński {"crc32", "-crc32 <file>", 1, &MainCrc32},
79*a03ca8b9SKrzysztof Kosiński };
80*a03ca8b9SKrzysztof Kosiński
81*a03ca8b9SKrzysztof Kosiński /******** GetPeakMemoryMetrics ********/
82*a03ca8b9SKrzysztof Kosiński
83*a03ca8b9SKrzysztof Kosiński #if defined(OS_LINUX) || defined(OS_CHROMEOS)
84*a03ca8b9SKrzysztof Kosiński // Linux does not have an exact mapping to the values used on Windows so use a
85*a03ca8b9SKrzysztof Kosiński // close approximation:
86*a03ca8b9SKrzysztof Kosiński // peak_virtual_memory ~= peak_page_file_usage
87*a03ca8b9SKrzysztof Kosiński // resident_set_size_hwm (high water mark) ~= peak_working_set_size
88*a03ca8b9SKrzysztof Kosiński //
89*a03ca8b9SKrzysztof Kosiński // On failure the input values will be set to 0.
GetPeakMemoryMetrics(size_t * peak_virtual_memory,size_t * resident_set_size_hwm)90*a03ca8b9SKrzysztof Kosiński void GetPeakMemoryMetrics(size_t* peak_virtual_memory,
91*a03ca8b9SKrzysztof Kosiński size_t* resident_set_size_hwm) {
92*a03ca8b9SKrzysztof Kosiński *peak_virtual_memory = 0;
93*a03ca8b9SKrzysztof Kosiński *resident_set_size_hwm = 0;
94*a03ca8b9SKrzysztof Kosiński auto status_path =
95*a03ca8b9SKrzysztof Kosiński base::FilePath("/proc")
96*a03ca8b9SKrzysztof Kosiński .Append(base::NumberToString(base::GetCurrentProcessHandle()))
97*a03ca8b9SKrzysztof Kosiński .Append("status");
98*a03ca8b9SKrzysztof Kosiński std::string contents_string;
99*a03ca8b9SKrzysztof Kosiński base::ReadFileToString(status_path, &contents_string);
100*a03ca8b9SKrzysztof Kosiński std::vector<base::StringPiece> lines = base::SplitStringPiece(
101*a03ca8b9SKrzysztof Kosiński contents_string, "\n", base::TRIM_WHITESPACE, base::SPLIT_WANT_NONEMPTY);
102*a03ca8b9SKrzysztof Kosiński
103*a03ca8b9SKrzysztof Kosiński for (const auto& line : lines) {
104*a03ca8b9SKrzysztof Kosiński // Tokens should generally be of the form "Metric: <val> kB"
105*a03ca8b9SKrzysztof Kosiński std::vector<base::StringPiece> tokens = base::SplitStringPiece(
106*a03ca8b9SKrzysztof Kosiński line, " ", base::TRIM_WHITESPACE, base::SPLIT_WANT_NONEMPTY);
107*a03ca8b9SKrzysztof Kosiński if (tokens.size() < 2)
108*a03ca8b9SKrzysztof Kosiński continue;
109*a03ca8b9SKrzysztof Kosiński
110*a03ca8b9SKrzysztof Kosiński if (tokens[0] == "VmPeak:") {
111*a03ca8b9SKrzysztof Kosiński if (base::StringToSizeT(tokens[1], peak_virtual_memory)) {
112*a03ca8b9SKrzysztof Kosiński *peak_virtual_memory *= 1024; // in kiB
113*a03ca8b9SKrzysztof Kosiński if (*resident_set_size_hwm)
114*a03ca8b9SKrzysztof Kosiński return;
115*a03ca8b9SKrzysztof Kosiński }
116*a03ca8b9SKrzysztof Kosiński } else if (tokens[0] == "VmHWM:") {
117*a03ca8b9SKrzysztof Kosiński if (base::StringToSizeT(tokens[1], resident_set_size_hwm)) {
118*a03ca8b9SKrzysztof Kosiński *resident_set_size_hwm *= 1024; // in kiB
119*a03ca8b9SKrzysztof Kosiński if (*peak_virtual_memory)
120*a03ca8b9SKrzysztof Kosiński return;
121*a03ca8b9SKrzysztof Kosiński }
122*a03ca8b9SKrzysztof Kosiński }
123*a03ca8b9SKrzysztof Kosiński }
124*a03ca8b9SKrzysztof Kosiński }
125*a03ca8b9SKrzysztof Kosiński #endif // defined(OS_LINUX) || defined(OS_CHROMEOS)
126*a03ca8b9SKrzysztof Kosiński
127*a03ca8b9SKrzysztof Kosiński #if defined(OS_WIN)
128*a03ca8b9SKrzysztof Kosiński // On failure the input values will be set to 0.
GetPeakMemoryMetrics(size_t * peak_page_file_usage,size_t * peak_working_set_size)129*a03ca8b9SKrzysztof Kosiński void GetPeakMemoryMetrics(size_t* peak_page_file_usage,
130*a03ca8b9SKrzysztof Kosiński size_t* peak_working_set_size) {
131*a03ca8b9SKrzysztof Kosiński *peak_page_file_usage = 0;
132*a03ca8b9SKrzysztof Kosiński *peak_working_set_size = 0;
133*a03ca8b9SKrzysztof Kosiński PROCESS_MEMORY_COUNTERS pmc;
134*a03ca8b9SKrzysztof Kosiński if (::GetProcessMemoryInfo(::GetCurrentProcess(), &pmc, sizeof(pmc))) {
135*a03ca8b9SKrzysztof Kosiński *peak_page_file_usage = pmc.PeakPagefileUsage;
136*a03ca8b9SKrzysztof Kosiński *peak_working_set_size = pmc.PeakWorkingSetSize;
137*a03ca8b9SKrzysztof Kosiński }
138*a03ca8b9SKrzysztof Kosiński }
139*a03ca8b9SKrzysztof Kosiński #endif // defined(OS_WIN)
140*a03ca8b9SKrzysztof Kosiński
141*a03ca8b9SKrzysztof Kosiński /******** ScopedResourceUsageTracker ********/
142*a03ca8b9SKrzysztof Kosiński
143*a03ca8b9SKrzysztof Kosiński // A class to track and log system resource usage.
144*a03ca8b9SKrzysztof Kosiński class ScopedResourceUsageTracker {
145*a03ca8b9SKrzysztof Kosiński public:
146*a03ca8b9SKrzysztof Kosiński // Initializes states for tracking.
ScopedResourceUsageTracker()147*a03ca8b9SKrzysztof Kosiński ScopedResourceUsageTracker() {
148*a03ca8b9SKrzysztof Kosiński start_time_ = base::TimeTicks::Now();
149*a03ca8b9SKrzysztof Kosiński
150*a03ca8b9SKrzysztof Kosiński #if defined(OS_LINUX) || defined(OS_CHROMEOS) || defined(OS_WIN)
151*a03ca8b9SKrzysztof Kosiński GetPeakMemoryMetrics(&start_peak_page_file_usage_,
152*a03ca8b9SKrzysztof Kosiński &start_peak_working_set_size_);
153*a03ca8b9SKrzysztof Kosiński #endif // defined(OS_LINUX) || defined(OS_CHROMEOS) || defined(OS_WIN)
154*a03ca8b9SKrzysztof Kosiński }
155*a03ca8b9SKrzysztof Kosiński
156*a03ca8b9SKrzysztof Kosiński // Computes and prints usage.
~ScopedResourceUsageTracker()157*a03ca8b9SKrzysztof Kosiński ~ScopedResourceUsageTracker() {
158*a03ca8b9SKrzysztof Kosiński base::TimeTicks end_time = base::TimeTicks::Now();
159*a03ca8b9SKrzysztof Kosiński
160*a03ca8b9SKrzysztof Kosiński #if defined(OS_LINUX) || defined(OS_CHROMEOS) || defined(OS_WIN)
161*a03ca8b9SKrzysztof Kosiński size_t cur_peak_page_file_usage = 0;
162*a03ca8b9SKrzysztof Kosiński size_t cur_peak_working_set_size = 0;
163*a03ca8b9SKrzysztof Kosiński GetPeakMemoryMetrics(&cur_peak_page_file_usage, &cur_peak_working_set_size);
164*a03ca8b9SKrzysztof Kosiński
165*a03ca8b9SKrzysztof Kosiński LOG(INFO) << "Zucchini.PeakPagefileUsage "
166*a03ca8b9SKrzysztof Kosiński << cur_peak_page_file_usage / 1024 << " KiB";
167*a03ca8b9SKrzysztof Kosiński LOG(INFO) << "Zucchini.PeakPagefileUsageChange "
168*a03ca8b9SKrzysztof Kosiński << (cur_peak_page_file_usage - start_peak_page_file_usage_) / 1024
169*a03ca8b9SKrzysztof Kosiński << " KiB";
170*a03ca8b9SKrzysztof Kosiński LOG(INFO) << "Zucchini.PeakWorkingSetSize "
171*a03ca8b9SKrzysztof Kosiński << cur_peak_working_set_size / 1024 << " KiB";
172*a03ca8b9SKrzysztof Kosiński LOG(INFO) << "Zucchini.PeakWorkingSetSizeChange "
173*a03ca8b9SKrzysztof Kosiński << (cur_peak_working_set_size - start_peak_working_set_size_) /
174*a03ca8b9SKrzysztof Kosiński 1024
175*a03ca8b9SKrzysztof Kosiński << " KiB";
176*a03ca8b9SKrzysztof Kosiński #endif // defined(OS_LINUX) || defined(OS_CHROMEOS) || defined(OS_WIN)
177*a03ca8b9SKrzysztof Kosiński
178*a03ca8b9SKrzysztof Kosiński LOG(INFO) << "Zucchini.TotalTime " << (end_time - start_time_).InSecondsF()
179*a03ca8b9SKrzysztof Kosiński << " s";
180*a03ca8b9SKrzysztof Kosiński }
181*a03ca8b9SKrzysztof Kosiński
182*a03ca8b9SKrzysztof Kosiński private:
183*a03ca8b9SKrzysztof Kosiński base::TimeTicks start_time_;
184*a03ca8b9SKrzysztof Kosiński #if defined(OS_LINUX) || defined(OS_CHROMEOS) || defined(OS_WIN)
185*a03ca8b9SKrzysztof Kosiński size_t start_peak_page_file_usage_ = 0;
186*a03ca8b9SKrzysztof Kosiński size_t start_peak_working_set_size_ = 0;
187*a03ca8b9SKrzysztof Kosiński #endif // defined(OS_LINUX) || defined(OS_CHROMEOS) || defined(OS_WIN)
188*a03ca8b9SKrzysztof Kosiński };
189*a03ca8b9SKrzysztof Kosiński
190*a03ca8b9SKrzysztof Kosiński /******** Helper functions ********/
191*a03ca8b9SKrzysztof Kosiński
192*a03ca8b9SKrzysztof Kosiński // Translates |command_line| arguments to a vector of base::FilePath (expecting
193*a03ca8b9SKrzysztof Kosiński // exactly |expected_count|). On success, writes the results to |paths| and
194*a03ca8b9SKrzysztof Kosiński // returns true. Otherwise returns false.
CheckAndGetFilePathParams(const base::CommandLine & command_line,size_t expected_count,std::vector<base::FilePath> * paths)195*a03ca8b9SKrzysztof Kosiński bool CheckAndGetFilePathParams(const base::CommandLine& command_line,
196*a03ca8b9SKrzysztof Kosiński size_t expected_count,
197*a03ca8b9SKrzysztof Kosiński std::vector<base::FilePath>* paths) {
198*a03ca8b9SKrzysztof Kosiński const base::CommandLine::StringVector& args = command_line.GetArgs();
199*a03ca8b9SKrzysztof Kosiński if (args.size() != expected_count)
200*a03ca8b9SKrzysztof Kosiński return false;
201*a03ca8b9SKrzysztof Kosiński
202*a03ca8b9SKrzysztof Kosiński paths->clear();
203*a03ca8b9SKrzysztof Kosiński paths->reserve(args.size());
204*a03ca8b9SKrzysztof Kosiński for (const auto& arg : args)
205*a03ca8b9SKrzysztof Kosiński paths->emplace_back(arg);
206*a03ca8b9SKrzysztof Kosiński return true;
207*a03ca8b9SKrzysztof Kosiński }
208*a03ca8b9SKrzysztof Kosiński
209*a03ca8b9SKrzysztof Kosiński // Prints main Zucchini usage text.
PrintUsage(std::ostream & err)210*a03ca8b9SKrzysztof Kosiński void PrintUsage(std::ostream& err) {
211*a03ca8b9SKrzysztof Kosiński err << "Version: " << zucchini::kMajorVersion << "."
212*a03ca8b9SKrzysztof Kosiński << zucchini::kMinorVersion << std::endl;
213*a03ca8b9SKrzysztof Kosiński err << "Usage:" << std::endl;
214*a03ca8b9SKrzysztof Kosiński for (const Command& command : kCommands)
215*a03ca8b9SKrzysztof Kosiński err << " zucchini " << command.usage << std::endl;
216*a03ca8b9SKrzysztof Kosiński }
217*a03ca8b9SKrzysztof Kosiński
218*a03ca8b9SKrzysztof Kosiński } // namespace
219*a03ca8b9SKrzysztof Kosiński
220*a03ca8b9SKrzysztof Kosiński /******** Exported Functions ********/
221*a03ca8b9SKrzysztof Kosiński
RunZucchiniCommand(const base::CommandLine & command_line,std::ostream & out,std::ostream & err)222*a03ca8b9SKrzysztof Kosiński zucchini::status::Code RunZucchiniCommand(const base::CommandLine& command_line,
223*a03ca8b9SKrzysztof Kosiński std::ostream& out,
224*a03ca8b9SKrzysztof Kosiński std::ostream& err) {
225*a03ca8b9SKrzysztof Kosiński // Look for a command with name that matches input.
226*a03ca8b9SKrzysztof Kosiński const Command* command_use = nullptr;
227*a03ca8b9SKrzysztof Kosiński for (const Command& command : kCommands) {
228*a03ca8b9SKrzysztof Kosiński if (command_line.HasSwitch(command.name)) {
229*a03ca8b9SKrzysztof Kosiński if (command_use) { // Too many commands found.
230*a03ca8b9SKrzysztof Kosiński command_use = nullptr; // Set to null to flag error.
231*a03ca8b9SKrzysztof Kosiński break;
232*a03ca8b9SKrzysztof Kosiński }
233*a03ca8b9SKrzysztof Kosiński command_use = &command;
234*a03ca8b9SKrzysztof Kosiński }
235*a03ca8b9SKrzysztof Kosiński }
236*a03ca8b9SKrzysztof Kosiński
237*a03ca8b9SKrzysztof Kosiński // Expect exactly 1 matching command. If 0 or >= 2, print usage and quit.
238*a03ca8b9SKrzysztof Kosiński if (!command_use) {
239*a03ca8b9SKrzysztof Kosiński err << "Must have exactly one of:" << std::endl;
240*a03ca8b9SKrzysztof Kosiński err << " [";
241*a03ca8b9SKrzysztof Kosiński zucchini::PrefixSep sep(", ");
242*a03ca8b9SKrzysztof Kosiński for (const Command& command : kCommands)
243*a03ca8b9SKrzysztof Kosiński err << sep << "-" << command.name;
244*a03ca8b9SKrzysztof Kosiński err << "]" << std::endl;
245*a03ca8b9SKrzysztof Kosiński PrintUsage(err);
246*a03ca8b9SKrzysztof Kosiński return zucchini::status::kStatusInvalidParam;
247*a03ca8b9SKrzysztof Kosiński }
248*a03ca8b9SKrzysztof Kosiński
249*a03ca8b9SKrzysztof Kosiński // Try to parse filename arguments. On failure, print usage and quit.
250*a03ca8b9SKrzysztof Kosiński std::vector<base::FilePath> paths;
251*a03ca8b9SKrzysztof Kosiński if (!CheckAndGetFilePathParams(command_line, command_use->num_args, &paths)) {
252*a03ca8b9SKrzysztof Kosiński err << command_use->usage << std::endl;
253*a03ca8b9SKrzysztof Kosiński PrintUsage(err);
254*a03ca8b9SKrzysztof Kosiński return zucchini::status::kStatusInvalidParam;
255*a03ca8b9SKrzysztof Kosiński }
256*a03ca8b9SKrzysztof Kosiński
257*a03ca8b9SKrzysztof Kosiński ScopedResourceUsageTracker resource_usage_tracker;
258*a03ca8b9SKrzysztof Kosiński return command_use->command_function({command_line, paths, out, err});
259*a03ca8b9SKrzysztof Kosiński }
260