xref: /aosp_15_r20/art/libarttools/art_exec.cc (revision 795d594fd825385562da6b089ea9b2033f3abf5a)
1 /*
2  * Copyright (C) 2022 The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 
17 #include <stdlib.h>
18 #include <sys/capability.h>
19 #include <sys/resource.h>
20 #include <unistd.h>
21 
22 #include <filesystem>
23 #include <iostream>
24 #include <iterator>
25 #include <optional>
26 #include <string>
27 #include <string_view>
28 #include <system_error>
29 #include <unordered_map>
30 #include <unordered_set>
31 #include <vector>
32 
33 #include "android-base/logging.h"
34 #include "android-base/parseint.h"
35 #include "android-base/result.h"
36 #include "android-base/strings.h"
37 #include "base/macros.h"
38 #include "base/os.h"
39 #include "base/scoped_cap.h"
40 #include "palette/palette.h"
41 #include "system/thread_defs.h"
42 
43 namespace {
44 
45 using ::android::base::ConsumePrefix;
46 using ::android::base::Join;
47 using ::android::base::ParseInt;
48 using ::android::base::Result;
49 using ::android::base::Split;
50 using ::art::OS;
51 
52 constexpr const char* kUsage =
53     R"(A wrapper binary that configures the process and executes a command.
54 
55 By default, it closes all open file descriptors except stdin, stdout, and stderr. `--keep-fds` can
56 be passed to keep some more file descriptors open.
57 
58 Usage: art_exec [OPTIONS]... -- [COMMAND]...
59 
60 Supported options:
61   --help: Print this text.
62   --set-task-profile=PROFILES: Apply a set of task profiles (see
63       https://source.android.com/devices/tech/perf/cgroups). Requires root access. PROFILES can be a
64       comma-separated list of task profile names.
65   --set-priority=PRIORITY: Apply the process priority. Currently, the only supported value of
66       PRIORITY is "background".
67   --drop-capabilities: Drop all root capabilities. Note that this has effect only if `art_exec` runs
68       with some root capabilities but not as the root user.
69   --keep-fds=FILE_DESCRIPTORS: A semicolon-separated list of file descriptors to keep open.
70   --env=KEY=VALUE: Set an environment variable. This flag can be passed multiple times to set
71       multiple environment variables.
72   --process-name-suffix=SUFFIX: Add a suffix in parentheses to argv[0] when calling `execv`. This
73       suffix will show up as part of the process name in tombstone when the process crashes.
74 )";
75 
76 constexpr int kErrorUsage = 100;
77 constexpr int kErrorOther = 101;
78 
79 struct Options {
80   int command_pos = -1;
81   std::vector<std::string> task_profiles;
82   std::optional<int> priority = std::nullopt;
83   bool drop_capabilities = false;
84   std::unordered_set<int> keep_fds{fileno(stdin), fileno(stdout), fileno(stderr)};
85   std::unordered_map<std::string, std::string> envs;
86   std::string chroot;
87   std::string process_name_suffix;
88 };
89 
Usage(const std::string & error_msg)90 [[noreturn]] void Usage(const std::string& error_msg) {
91   LOG(ERROR) << error_msg;
92   std::cerr << error_msg << "\n" << kUsage << "\n";
93   exit(kErrorUsage);
94 }
95 
ParseOptions(int argc,char ** argv)96 Options ParseOptions(int argc, char** argv) {
97   Options options;
98   for (int i = 1; i < argc; i++) {
99     std::string_view arg = argv[i];
100     if (arg == "--help") {
101       std::cerr << kUsage << "\n";
102       exit(0);
103     } else if (ConsumePrefix(&arg, "--set-task-profile=")) {
104       options.task_profiles = Split(std::string(arg), ",");
105       if (options.task_profiles.empty()) {
106         Usage("Empty task profile list");
107       }
108     } else if (ConsumePrefix(&arg, "--set-priority=")) {
109       if (arg == "background") {
110         options.priority = ANDROID_PRIORITY_BACKGROUND;
111       } else {
112         Usage("Unknown priority " + std::string(arg));
113       }
114     } else if (arg == "--drop-capabilities") {
115       options.drop_capabilities = true;
116     } else if (ConsumePrefix(&arg, "--keep-fds=")) {
117       for (const std::string& fd_str : Split(std::string(arg), ":")) {
118         int fd;
119         if (!ParseInt(fd_str, &fd)) {
120           Usage("Invalid fd " + fd_str);
121         }
122         options.keep_fds.insert(fd);
123       }
124     } else if (ConsumePrefix(&arg, "--env=")) {
125       size_t pos = arg.find('=');
126       if (pos == std::string_view::npos) {
127         Usage("Malformed environment variable. Must contain '='");
128       }
129       options.envs[std::string(arg.substr(/*pos=*/0, /*n=*/pos))] =
130           std::string(arg.substr(pos + 1));
131     } else if (ConsumePrefix(&arg, "--chroot=")) {
132       options.chroot = arg;
133     } else if (ConsumePrefix(&arg, "--process-name-suffix=")) {
134       options.process_name_suffix = arg;
135     } else if (arg == "--") {
136       if (i + 1 >= argc) {
137         Usage("Missing command after '--'");
138       }
139       options.command_pos = i + 1;
140       return options;
141     } else {
142       Usage("Unknown option " + std::string(arg));
143     }
144   }
145   Usage("Missing '--'");
146 }
147 
DropInheritableCaps()148 Result<void> DropInheritableCaps() {
149   art::ScopedCap cap(cap_get_proc());
150   if (cap.Get() == nullptr) {
151     return ErrnoErrorf("Failed to call cap_get_proc");
152   }
153   if (cap_clear_flag(cap.Get(), CAP_INHERITABLE) != 0) {
154     return ErrnoErrorf("Failed to call cap_clear_flag");
155   }
156   if (cap_set_proc(cap.Get()) != 0) {
157     return ErrnoErrorf("Failed to call cap_set_proc");
158   }
159   return {};
160 }
161 
CloseFds(const std::unordered_set<int> & keep_fds)162 Result<void> CloseFds(const std::unordered_set<int>& keep_fds) {
163   std::vector<int> open_fds;
164   std::error_code ec;
165   for (const std::filesystem::directory_entry& dir_entry :
166        std::filesystem::directory_iterator("/proc/self/fd", ec)) {
167     int fd;
168     if (!ParseInt(dir_entry.path().filename(), &fd)) {
169       return Errorf("Invalid entry in /proc/self/fd {}", dir_entry.path().filename());
170     }
171     open_fds.push_back(fd);
172   }
173   if (ec) {
174     return Errorf("Failed to list open FDs: {}", ec.message());
175   }
176   for (int fd : open_fds) {
177     if (keep_fds.find(fd) == keep_fds.end()) {
178       if (close(fd) != 0) {
179         Result<void> error = ErrnoErrorf("Failed to close FD {}", fd);
180         if (std::filesystem::exists(ART_FORMAT("/proc/self/fd/{}", fd))) {
181           return error;
182         }
183       }
184     }
185   }
186   return {};
187 }
188 
189 }  // namespace
190 
main(int argc,char ** argv)191 int main(int argc, char** argv) {
192   android::base::InitLogging(argv);
193 
194   Options options = ParseOptions(argc, argv);
195 
196   if (auto result = CloseFds(options.keep_fds); !result.ok()) {
197     LOG(ERROR) << "Failed to close open FDs: " << result.error();
198     return kErrorOther;
199   }
200 
201   if (!options.task_profiles.empty()) {
202     if (int ret = PaletteSetTaskProfiles(/*tid=*/0, options.task_profiles);
203         ret != PALETTE_STATUS_OK) {
204       LOG(ERROR) << "Failed to set task profile: " << ret;
205       return kErrorOther;
206     }
207   }
208 
209   if (options.priority.has_value()) {
210     if (setpriority(PRIO_PROCESS, /*who=*/0, options.priority.value()) != 0) {
211       PLOG(ERROR) << "Failed to setpriority";
212       return kErrorOther;
213     }
214   }
215 
216   if (options.drop_capabilities) {
217     if (auto result = DropInheritableCaps(); !result.ok()) {
218       LOG(ERROR) << "Failed to drop inheritable capabilities: " << result.error();
219       return kErrorOther;
220     }
221   }
222 
223   for (const auto& [key, value] : options.envs) {
224     setenv(key.c_str(), value.c_str(), /*overwrite=*/1);
225   }
226 
227   if (!options.chroot.empty()) {
228     if (chroot(options.chroot.c_str()) != 0) {
229       PLOG(ERROR) << ART_FORMAT("Failed to chroot to '{}'", options.chroot);
230       return kErrorOther;
231     }
232   }
233 
234   // `argv[argc]` is `nullptr`, which `execv` needs.
235   std::vector<char*> command_args(&argv[options.command_pos], &argv[argc + 1]);
236   std::string program_path = argv[options.command_pos];
237   // "/mnt/compat_env" is prepared by dexopt_chroot_setup on Android V.
238   constexpr const char* kCompatArtdPath = "/mnt/compat_env/apex/com.android.art/bin/artd";
239   if (program_path == "/apex/com.android.art/bin/artd" && OS::FileExists(kCompatArtdPath)) {
240     LOG(INFO) << "Overriding program path to " << kCompatArtdPath;
241     program_path = kCompatArtdPath;
242     command_args[0] = program_path.data();
243   }
244   std::string override_program_name;
245   if (!options.process_name_suffix.empty()) {
246     override_program_name = ART_FORMAT("{} ({})", command_args[0], options.process_name_suffix);
247     command_args[0] = override_program_name.data();
248   }
249 
250   execv(program_path.c_str(), command_args.data());
251 
252   // Remove the trialing `nullptr`.
253   command_args.resize(command_args.size() - 1);
254 
255   PLOG(FATAL) << "Failed to execute (" << Join(command_args, ' ') << ")";
256   UNREACHABLE();
257 }
258