1 // Copyright (C) 2020 The Android Open Source Project
2 //
3 // Licensed under the Apache License, Version 2.0 (the "License");
4 // you may not use this file except in compliance with the License.
5 // You may obtain a copy of the License at
6 //
7 // http://www.apache.org/licenses/LICENSE-2.0
8 //
9 // Unless required by applicable law or agreed to in writing, software
10 // distributed under the License is distributed on an "AS IS" BASIS,
11 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 // See the License for the specific language governing permissions and
13 // limitations under the License.
14
15 #include "common/cmd_utils.h"
16 #include "inode2filename/inode_resolver.h"
17 #include "inode2filename/out_of_process_inode_resolver.h"
18
19 #include <android-base/file.h>
20 #include <android-base/logging.h>
21 #include <android-base/unique_fd.h>
22
23 #include <sstream>
24 #include <stdio.h>
25 #include <unistd.h>
26
27 namespace rx = rxcpp;
28
29 namespace iorap::inode2filename {
30
31 using ::android::base::unique_fd;
32 using ::android::base::borrowed_fd;
33
GetCommandFileName()34 static const char* GetCommandFileName() {
35 // Avoid ENOENT by execve by specifying the absolute path of inode2filename.
36 #ifdef __ANDROID__
37 return "/system/bin/iorap.inode2filename";
38 #else
39 static const char* file_name = nullptr;
40
41 if (file_name == nullptr) {
42 char* out_dir = getenv("ANDROID_HOST_OUT");
43 static std::string file_name_str = out_dir;
44 if (out_dir != nullptr) {
45 file_name_str += "/bin/";
46 } else {
47 // Assume it's in the same directory as the binary we are in.
48 std::string self_path;
49 CHECK(::android::base::Readlink("/proc/self/exe", /*out*/&self_path));
50
51 std::string self_dir = ::android::base::Dirname(self_path);
52 file_name_str = self_dir + "/";
53 }
54 file_name_str += "iorap.inode2filename";
55
56 file_name = file_name_str.c_str();
57 }
58
59 return file_name;
60 #endif
61 }
62
ErrorCodeFromErrno()63 std::error_code ErrorCodeFromErrno() {
64 int err = errno;
65 return std::error_code(err, std::system_category());
66 }
67
IosBaseFailureWithErrno(const char * message)68 std::ios_base::failure IosBaseFailureWithErrno(const char* message) {
69 std::error_code ec = ErrorCodeFromErrno();
70 return std::ios_base::failure(message, ec);
71 }
72
73 static constexpr bool kDebugFgets = false;
74
ReadLineLength(FILE * stream,bool * eof)75 int32_t ReadLineLength(FILE* stream, bool* eof) {
76 char buf[sizeof(int32_t)];
77 size_t count = fread(buf, 1, sizeof(int32_t), stream);
78 if (feof(stream)) {
79 // If reaching the end of the stream when trying to read the first int, just
80 // return. This is legitimate, because after reading the last line, the next
81 // iteration will reach this.
82 *eof = true;
83 return 0;
84 }
85 int32_t length;
86 memcpy(&length, buf, sizeof(int32_t));
87 return length;
88 }
89
90 // The steam is like [size1][file1][size2][file2]...[sizeN][fileN].
ReadOneLine(FILE * stream,bool * eof)91 std::string ReadOneLine(FILE* stream, bool* eof) {
92 DCHECK(stream != nullptr);
93 DCHECK(eof != nullptr);
94
95 int32_t length = ReadLineLength(stream, eof);
96 if (length <= 0) {
97 PLOG(ERROR) << "unexpected 0 length line.";
98 *eof = true;
99 return "";
100 }
101
102 std::string str(length, '\0');
103 size_t count = fread(&str[0], sizeof(char), length, stream);
104 if (feof(stream) || ferror(stream) || count != (uint32_t)length) {
105 // error! :(
106 PLOG(ERROR) << "unexpected end of the line during fread";
107 *eof = true;
108 return "";
109 }
110 return str;
111 }
112
LeftTrim(std::string & s)113 static inline void LeftTrim(/*inout*/std::string& s) {
114 s.erase(s.begin(), std::find_if(s.begin(), s.end(), [](int ch) {
115 return !std::isspace(ch);
116 }));
117 }
118
119 // Parses an --output-format=ipc kind of line into an InodeResult.
120 // Returns nullopt if parsing failed.
ParseFromLine(const std::string & line)121 std::optional<InodeResult> ParseFromLine(const std::string& line) {
122 // inode <- INT:INT:INT
123 // line_ok <- 'K ' inode ' ' STRING
124 // line_err <- 'E ' inode ' ' INT
125 //
126 // result <- line_ok | line_err
127
128 std::stringstream ss{line};
129
130 bool result_ok = false;
131
132 std::string ok_or_error;
133 ss >> ok_or_error;
134
135 if (ss.fail()) {
136 return std::nullopt;
137 }
138
139 if (ok_or_error == "K") {
140 result_ok = true;
141 } else if (ok_or_error == "E") {
142 result_ok = false;
143 } else {
144 return std::nullopt;
145 }
146
147 std::string inode_s;
148 ss >> inode_s;
149
150 if (ss.fail()) {
151 return std::nullopt;
152 }
153
154 Inode inode;
155
156 std::string inode_parse_error_msg;
157 if (!Inode::Parse(inode_s, /*out*/&inode, /*out*/&inode_parse_error_msg)) {
158 return std::nullopt;
159 }
160
161 if (result_ok == false) {
162 int error_code;
163 ss >> error_code;
164
165 if (ss.fail()) {
166 return std::nullopt;
167 }
168
169 return InodeResult::makeFailure(inode, error_code);
170 } else if (result_ok == true) {
171 std::string rest_of_line;
172 ss >> rest_of_line;
173
174 // parse " string with potential spaces"
175 // into "string with potential spaces"
176 LeftTrim(/*inout*/rest_of_line);
177
178 if (ss.fail()) {
179 return std::nullopt;
180 }
181
182 const std::string& file_name = rest_of_line;
183
184 return InodeResult::makeSuccess(inode, file_name);
185 }
186
187 return std::nullopt;
188 }
189
190 struct OutOfProcessInodeResolver::Impl {
Impliorap::inode2filename::OutOfProcessInodeResolver::Impl191 Impl() {
192 }
193
194 private:
195 // Create the argv we will pass to the forked inode2filename, corresponds to #EmitAll.
CreateArgvAlliorap::inode2filename::OutOfProcessInodeResolver::Impl196 std::vector<std::string> CreateArgvAll(const InodeResolverDependencies& deps) const {
197 std::vector<std::string> argv;
198 argv.push_back("--all");
199
200 return CreateArgv(deps, std::move(argv));
201 }
202
203 // Create the argv we will pass to the forked inode2filename, corresponds to
204 // #FindFilenamesFromInodes.
CreateArgvFindiorap::inode2filename::OutOfProcessInodeResolver::Impl205 std::vector<std::string> CreateArgvFind(const InodeResolverDependencies& deps,
206 const std::vector<Inode>& inodes) const {
207 std::vector<std::string> argv;
208 iorap::common::AppendArgsRepeatedly(argv, inodes);
209
210 return CreateArgv(deps, std::move(argv));
211 }
212
CreateArgviorap::inode2filename::OutOfProcessInodeResolver::Impl213 std::vector<std::string> CreateArgv(const InodeResolverDependencies& deps,
214 std::vector<std::string> append_argv) const {
215 InodeResolverDependencies deps_oop = deps;
216 deps_oop.process_mode = ProcessMode::kInProcessDirect;
217
218 std::vector<std::string> argv = ToArgs(deps_oop);
219
220 argv.push_back("--output-format=ipc");
221
222 if (iorap::common::GetBoolEnvOrProperty("iorap.inode2filename.log.verbose", false)) {
223 argv.push_back("--verbose");
224 }
225
226 iorap::common::AppendArgsRepeatedly(argv, std::move(append_argv));
227
228 return argv;
229 }
230
231 public:
232 // fork+exec into inode2filename with 'inodes' as the search list.
233 // Each result is parsed into a dest#on_next(result).
234 // If a fatal error occurs, dest#on_error is called once and no other callbacks are called.
EmitFromCommandFindiorap::inode2filename::OutOfProcessInodeResolver::Impl235 void EmitFromCommandFind(rxcpp::subscriber<InodeResult>& dest,
236 const InodeResolverDependencies& deps,
237 const std::vector<Inode>& inodes) {
238 // Trivial case: complete immediately.
239 // Executing inode2filename with empty search list will just print the --help menu.
240 if (inodes.empty()) {
241 dest.on_completed();
242 }
243
244 std::vector<std::string> argv = CreateArgvFind(deps, inodes);
245 EmitFromCommandWithArgv(/*inout*/dest, std::move(argv), inodes.size());
246 }
247
248 // fork+exec into inode2filename with --all (listing *all* inodes).
249 // Each result is parsed into a dest#on_next(result).
250 // If a fatal error occurs, dest#on_error is called once and no other callbacks are called.
EmitFromCommandAlliorap::inode2filename::OutOfProcessInodeResolver::Impl251 void EmitFromCommandAll(rxcpp::subscriber<InodeResult>& dest,
252 const InodeResolverDependencies& deps) {
253 std::vector<std::string> argv = CreateArgvAll(deps);
254 EmitFromCommandWithArgv(/*inout*/dest, std::move(argv), /*result_count*/std::nullopt);
255 }
256
257 private:
EmitFromCommandWithArgviorap::inode2filename::OutOfProcessInodeResolver::Impl258 void EmitFromCommandWithArgv(rxcpp::subscriber<InodeResult>& dest,
259 std::vector<std::string> argv_vec,
260 std::optional<size_t> result_count) {
261 unique_fd pipe_reader, pipe_writer;
262 if (!::android::base::Pipe(/*out*/&pipe_reader, /*out*/&pipe_writer)) {
263 dest.on_error(
264 rxcpp::util::make_error_ptr(
265 IosBaseFailureWithErrno("Failed to create out-going pipe for inode2filename")));
266 return;
267 }
268
269 pid_t child = fork();
270 if (child == -1) {
271 dest.on_error(
272 rxcpp::util::make_error_ptr(
273 IosBaseFailureWithErrno("Failed to fork process for inode2filename")));
274 return;
275 } else if (child > 0) { // we are the caller of this function
276 LOG(DEBUG) << "forked into a process for inode2filename , pid = " << child;
277 } else {
278 // we are the child that was forked
279
280 const char* kCommandFileName = GetCommandFileName();
281
282 std::stringstream argv; // for debugging.
283 for (std::string arg : argv_vec) {
284 argv << arg << ' ';
285 }
286 LOG(DEBUG) << "fork+exec: " << kCommandFileName << " " << argv.str();
287
288 // Redirect only stdout. stdin is unused, stderr is same as parent.
289 if (dup2(pipe_writer.get(), STDOUT_FILENO) == -1) {
290 // Trying to call #on_error does not make sense here because we are in a forked process,
291 // the only thing we can do is crash definitively.
292 PLOG(FATAL) << "Failed to dup2 for inode2filename";
293 }
294
295 std::unique_ptr<const char *[]> argv_ptr =
296 common::VecToArgv(kCommandFileName, argv_vec);
297
298 if (execve(kCommandFileName,
299 (char **)argv_ptr.get(),
300 /*envp*/nullptr) == -1) {
301 // Trying to call #on_error does not make sense here because we are in a forked process,
302 // the only thing we can do is crash definitively.
303 PLOG(FATAL) << "Failed to execve process for inode2filename";
304 }
305 // This should never return.
306 }
307
308 // Immediately close the writer end of the pipe because we never use it.
309 pipe_writer.reset();
310
311 // Convert pipe(reader) file descriptor into FILE*.
312 std::unique_ptr<FILE, int(*)(FILE*)> file_reader{
313 ::android::base::Fdopen(std::move(pipe_reader), /*mode*/"r"), fclose};
314 if (!file_reader) {
315 dest.on_error(
316 rxcpp::util::make_error_ptr(
317 IosBaseFailureWithErrno("Failed to fdopen for inode2filename")));
318 return;
319 }
320
321 size_t actual_result_count = 0;
322
323 bool file_eof = false;
324 while (!file_eof) {
325 std::string inode2filename_line = ReadOneLine(file_reader.get(), /*out*/&file_eof);
326
327 if (inode2filename_line.empty()) {
328 if (!file_eof) {
329 // Ignore empty lines.
330 LOG(WARNING) << "inode2filename: got empty line";
331 }
332 continue;
333 }
334
335 LOG(DEBUG) << "inode2filename output-line: " << inode2filename_line;
336
337 std::optional<InodeResult> res = ParseFromLine(inode2filename_line);
338 if (!res) {
339 std::string error_msg = "Invalid output: ";
340 error_msg += inode2filename_line;
341 dest.on_error(
342 rxcpp::util::make_error_ptr(std::ios_base::failure(error_msg)));
343 return;
344 }
345 dest.on_next(*res);
346
347 ++actual_result_count;
348 }
349
350 LOG(DEBUG) << "inode2filename output-eof";
351
352 // Ensure that the # of inputs into the rx stream match the # of outputs.
353 // This is validating the post-condition of FindFilenamesFromInodes.
354 if (result_count && actual_result_count != *result_count) {
355 std::stringstream ss;
356 ss << "Invalid number of results, expected: " << *result_count;
357 ss << ", actual: " << actual_result_count;
358
359 dest.on_error(
360 rxcpp::util::make_error_ptr(std::ios_base::failure(ss.str())));
361 return;
362 }
363
364 CHECK(child > 0); // we are in the parent process, parse the IPC output of inode2filename
365 dest.on_completed();
366 }
367 };
368
369 rxcpp::observable<InodeResult>
FindFilenamesFromInodes(std::vector<Inode> inodes) const370 OutOfProcessInodeResolver::FindFilenamesFromInodes(std::vector<Inode> inodes) const {
371 return rxcpp::observable<>::create<InodeResult>(
372 [self=std::static_pointer_cast<const OutOfProcessInodeResolver>(shared_from_this()),
373 inodes=std::move(inodes)](
374 rxcpp::subscriber<InodeResult> s) {
375 self->impl_->EmitFromCommandFind(s, self->GetDependencies(), inodes);
376 });
377 }
378
379 rxcpp::observable<InodeResult>
EmitAll() const380 OutOfProcessInodeResolver::EmitAll() const {
381 auto self = std::static_pointer_cast<const OutOfProcessInodeResolver>(shared_from_this());
382 CHECK(self != nullptr);
383 CHECK(self->impl_ != nullptr);
384
385 return rxcpp::observable<>::create<InodeResult>(
386 [self](rxcpp::subscriber<InodeResult> s) {
387 CHECK(self != nullptr);
388 CHECK(self->impl_ != nullptr);
389 self->impl_->EmitFromCommandAll(s, self->GetDependencies());
390 });
391 }
392
OutOfProcessInodeResolver(InodeResolverDependencies dependencies)393 OutOfProcessInodeResolver::OutOfProcessInodeResolver(InodeResolverDependencies dependencies)
394 : InodeResolver{std::move(dependencies)}, impl_{new Impl{}} {
395 }
396
~OutOfProcessInodeResolver()397 OutOfProcessInodeResolver::~OutOfProcessInodeResolver() {
398 // std::unique_ptr requires complete types, but we hide the definition in the header.
399 delete impl_;
400 }
401
402 } // namespace iorap::inode2filename
403