• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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