1 // Copyright (C) 2018 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 #ifndef IORAP_SRC_INODE2FILENAME_SEARCH_DIRECTORIES_H_
16 #define IORAP_SRC_INODE2FILENAME_SEARCH_DIRECTORIES_H_
17 
18 #include "common/expected.h"
19 #include "inode2filename/inode.h"
20 #include "inode2filename/system_call.h"
21 
22 #include <fruit/fruit.h>
23 
24 #include <rxcpp/rx.hpp>
25 namespace iorap::inode2filename {
26 
27 // Tuple of (Inode -> (Filename|Errno))
28 struct InodeResult {
29   // We set this error when all root directories have been searched and
30   // yet we still could not find a corresponding filename for the inode under search.
31   static constexpr int kCouldNotFindFilename = -ENOENT;
32 
33   Inode inode;
34   // Value: Contains the filename (with a root directory as a prefix).
35   // Error: Contains the errno, usually -ENOENT or perhaps a security error.
36   iorap::expected<std::string /*filename*/, int /*errno*/> data;
37 
makeSuccessInodeResult38   static InodeResult makeSuccess(Inode inode, std::string filename) {
39     return InodeResult{inode, std::move(filename)};
40   }
41 
makeFailureInodeResult42   static InodeResult makeFailure(Inode inode, int err_no) {
43     return InodeResult{inode, iorap::unexpected{err_no}};
44   }
45 
46   constexpr operator bool() const {
47     return data.has_value();
48   }
49 };
50 
51 enum class SearchMode {
52   // Test modes:
53   kInProcessDirect,  // Execute the code directly.
54   kInProcessIpc,     // Execute code via IPC layer using multiple threads.
55   // Shipping mode:
56   kOutOfProcessIpc,  // Execute code via fork+exec with IPC.
57 
58   // Note: in-process system-wide stat(2)/readdir/etc is blocked by selinux.
59   // Attempting to call the test modes will fail with -EPERM.
60   //
61   // Use fork+exec mode in shipping configurations, which spawns inode2filename
62   // as a separate command.
63 };
64 
65 struct SearchDirectories {
66   // Type-erased subset of rxcpp::connectable_observable<?>
67   struct RxAnyConnectable {
68     // Connects to the underlying observable.
69     //
70     // This kicks off the graph, streams begin emitting items.
71     // This method will block until all items have been fully emitted
72     // and processed by any subscribers.
73     virtual void connect() = 0;
74 
~RxAnyConnectableSearchDirectories::RxAnyConnectable75     virtual ~RxAnyConnectable(){}
76   };
77 
78 
79   // Create a cold observable of inode results (a lazy stream) corresponding
80   // to the inode search list.
81   //
82   // A depth-first search is done on each of the root directories (in order),
83   // until all inodes have been found (or until all directories have been exhausted).
84   //
85   // Some internal errors may occur during emission that aren't part of an InodeResult;
86   // these will be sent to the error logcat and dropped.
87   //
88   // Calling this function does not begin the search.
89   // The returned observable will begin the search after subscribing to it.
90   //
91   // The emitted InodeResult stream has these guarantees:
92   // - All inodes in inode_list will eventually be emitted exactly once in an InodeResult
93   // - When all inodes are found, directory traversal is halted.
94   // - The order of emission can be considered arbitrary.
95   //
96   // Lifetime rules:
97   // - The observable must be fully consumed before deleting any of the SearchDirectory's
98   //   borrowed constructor parameters (e.g. the SystemCall).
99   // - SearchDirectory itself can be deleted at any time after creating an observable.
100   rxcpp::observable<InodeResult>
101       FindFilenamesFromInodes(std::vector<std::string> root_directories,
102                               std::vector<Inode> inode_list,
103                               SearchMode mode);
104 
105   // Create a cold observable of inode results (a lazy stream) corresponding
106   // to the inode search list.
107   //
108   // A depth-first search is done on each of the root directories (in order),
109   // until all inodes have been found (or until all directories have been exhausted).
110   //
111   // Some internal errors may occur during emission that aren't part of an InodeResult;
112   // these will be sent to the error logcat and dropped.
113   //
114   // Calling this function does not begin the search.
115   // The returned observable will begin the search after subscribing to it.
116   //
117   // The emitted InodeResult stream has these guarantees:
118   // - All inodes in inode_list will eventually be emitted exactly once in an InodeResult
119   // - When all inodes are found, directory traversal is halted.
120   // - The order of emission can be considered arbitrary.
121   //
122   // Lifetime rules:
123   // - The observable must be fully consumed before deleting any of the SearchDirectory's
124   //   borrowed constructor parameters (e.g. the SystemCall).
125   // - SearchDirectory itself can be deleted at any time after creating an observable.
126   std::pair<rxcpp::observable<InodeResult>, std::unique_ptr<RxAnyConnectable>>
127       FindFilenamesFromInodesPair(std::vector<std::string> root_directories,
128                                   std::vector<Inode> inode_list,
129                                   SearchMode mode);
130 
131   // Any borrowed parameters here can also be borrowed by the observables returned by the above
132   // member functions.
133   //
134   // The observables must be fully consumed within the lifetime of the borrowed parameters.
INJECTSearchDirectories135   INJECT(SearchDirectories(borrowed<SystemCall*> system_call))
136       : system_call_(system_call) {}
137 
138   // TODO: is there a way to get rid of this second RxAnyConnectable parameter?
139  private:
140   // This gets passed around to lazy lambdas, so we must finish consuming any observables
141   // before the injected system call is deleted.
142   borrowed<SystemCall*> system_call_;
143 };
144 
145 }  // namespace iorap::inode2filename
146 
147 #endif  // IORAP_SRC_INODE2FILENAME_SEARCH_DIRECTORIES_H_
148