1 //
2 // Copyright (C) 2024 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 #include "host/commands/run_cvd/launch/launch.h"
17
18 #include <sys/socket.h>
19
20 #include <regex>
21 #include <utility>
22 #include <vector>
23
24 #include <android-base/file.h>
25 #include <fruit/fruit.h>
26
27 #include "common/libs/utils/result.h"
28 #include "common/libs/utils/subprocess.h"
29 #include "host/commands/run_cvd/launch/input_connections_provider.h"
30 #include "host/libs/config/command_source.h"
31 #include "host/libs/config/known_paths.h"
32
33 namespace cuttlefish {
34 namespace {
35
36 using Subprocess::StdIOChannel::kStdErr;
37
38 // Holds all sockets related to a single vhost user input device process.
39 struct DeviceSockets {
40 // Device end of the connection between device and streamer.
41 SharedFD device_end;
42 // Streamer end of the connection between device and streamer.
43 SharedFD streamer_end;
44 // Unix socket for the server to which the VMM connects to. It's created and
45 // held at the CommandSource level to ensure it already exists by the time the
46 // VMM runs and attempts to connect.
47 SharedFD vhu_server;
48 };
49
NewDeviceSockets(const std::string & vhu_server_path)50 Result<DeviceSockets> NewDeviceSockets(const std::string& vhu_server_path) {
51 DeviceSockets ret;
52 CF_EXPECTF(
53 SharedFD::SocketPair(AF_UNIX, SOCK_STREAM, 0, &ret.device_end,
54 &ret.streamer_end),
55 "Failed to create connection sockets (socket pair) for input device: {}",
56 ret.device_end->StrError());
57
58 // The webRTC process currently doesn't read status updates from input
59 // devices, so the vhost processes will write that to /dev/null.
60 // These calls shouldn't return errors since we already know these are a newly
61 // created socket pair.
62 CF_EXPECTF(ret.device_end->Shutdown(SHUT_WR) == 0,
63 "Failed to close input connection's device for writes: {}",
64 ret.device_end->StrError());
65 CF_EXPECTF(ret.streamer_end->Shutdown(SHUT_RD) == 0,
66 "Failed to close input connection's streamer end for reads: {}",
67 ret.streamer_end->StrError());
68
69 ret.vhu_server =
70 SharedFD::SocketLocalServer(vhu_server_path, false, SOCK_STREAM, 0600);
71 CF_EXPECTF(ret.vhu_server->IsOpen(),
72 "Failed to create vhost user socket for device: {}",
73 ret.vhu_server->StrError());
74
75 return ret;
76 }
77
NewVhostUserInputCommand(const DeviceSockets & device_sockets,const std::string & spec)78 Command NewVhostUserInputCommand(const DeviceSockets& device_sockets,
79 const std::string& spec) {
80 Command cmd(VhostUserInputBinary());
81 cmd.AddParameter("--verbosity=DEBUG");
82 cmd.AddParameter("--socket-fd=", device_sockets.vhu_server);
83 cmd.AddParameter("--device-config=", spec);
84 cmd.RedirectStdIO(Subprocess::StdIOChannel::kStdIn,
85 device_sockets.device_end);
86 cmd.RedirectStdIO(Subprocess::StdIOChannel::kStdOut,
87 SharedFD::Open("/dev/null", O_WRONLY));
88 return cmd;
89 }
90
91 struct TemplateVars {
92 int index;
93 int width;
94 int height;
95 };
96
BuildTouchSpec(const std::string & spec_template,TemplateVars vars)97 std::string BuildTouchSpec(const std::string& spec_template,
98 TemplateVars vars) {
99 std::pair<std::string, int> replacements[] = {{"%INDEX%", vars.index},
100 {"%WIDTH%", vars.width},
101 {"%HEIGHT%", vars.height}};
102 std::string spec = spec_template;
103 for (const auto& [key, value] : replacements) {
104 spec = std::regex_replace(spec, std::regex(key), std::to_string(value));
105 }
106 return spec;
107 }
108
109 // Creates the commands for the vhost user input devices.
110 class VhostInputDevices : public CommandSource,
111 public InputConnectionsProvider {
112 public:
INJECT(VhostInputDevices (const CuttlefishConfig::InstanceSpecific & instance,LogTeeCreator & log_tee))113 INJECT(VhostInputDevices(const CuttlefishConfig::InstanceSpecific& instance,
114 LogTeeCreator& log_tee))
115 : instance_(instance), log_tee_(log_tee) {}
116
117 // CommandSource
Commands()118 Result<std::vector<MonitorCommand>> Commands() override {
119 std::vector<MonitorCommand> commands;
120 Command rotary_cmd =
121 NewVhostUserInputCommand(rotary_sockets_, DefaultRotaryDeviceSpec());
122 Command rotary_log_tee = CF_EXPECT(
123 log_tee_.CreateLogTee(rotary_cmd, "vhost_user_rotary", kStdErr),
124 "Failed to create log tee command for rotary device");
125 commands.emplace_back(std::move(rotary_cmd));
126 commands.emplace_back(std::move(rotary_log_tee));
127
128 if (instance_.enable_mouse()) {
129 Command mouse_cmd =
130 NewVhostUserInputCommand(mouse_sockets_, DefaultMouseSpec());
131 Command mouse_log_tee = CF_EXPECT(
132 log_tee_.CreateLogTee(mouse_cmd, "vhost_user_mouse", kStdErr),
133 "Failed to create log tee command for mouse device");
134 commands.emplace_back(std::move(mouse_cmd));
135 commands.emplace_back(std::move(mouse_log_tee));
136 }
137
138 std::string keyboard_spec =
139 instance_.custom_keyboard_config().value_or(DefaultKeyboardSpec());
140 Command keyboard_cmd =
141 NewVhostUserInputCommand(keyboard_sockets_, keyboard_spec);
142 Command keyboard_log_tee = CF_EXPECT(
143 log_tee_.CreateLogTee(keyboard_cmd, "vhost_user_keyboard", kStdErr),
144 "Failed to create log tee command for keyboard device");
145 commands.emplace_back(std::move(keyboard_cmd));
146 commands.emplace_back(std::move(keyboard_log_tee));
147
148 Command switches_cmd =
149 NewVhostUserInputCommand(switches_sockets_, DefaultSwitchesSpec());
150 Command switches_log_tee = CF_EXPECT(
151 log_tee_.CreateLogTee(switches_cmd, "vhost_user_switches", kStdErr),
152 "Failed to create log tee command for switches device");
153 commands.emplace_back(std::move(switches_cmd));
154 commands.emplace_back(std::move(switches_log_tee));
155
156 const bool use_multi_touch =
157 instance_.guest_os() !=
158 CuttlefishConfig::InstanceSpecific::GuestOs::ChromeOs;
159
160 std::string touchscreen_template_path =
161 use_multi_touch ? DefaultMultiTouchscreenSpecTemplate()
162 : DefaultSingleTouchscreenSpecTemplate();
163 const std::string touchscreen_template = CF_EXPECTF(
164 ReadFileContents(touchscreen_template_path),
165 "Failed to load touchscreen template: {}", touchscreen_template_path);
166 for (int i = 0; i < instance_.display_configs().size(); ++i) {
167 const int width = instance_.display_configs()[i].width;
168 const int height = instance_.display_configs()[i].height;
169 const std::string spec = BuildTouchSpec(
170 touchscreen_template, {.index = i, .width = width, .height = height});
171 const std::string spec_path = instance_.PerInstanceInternalPath(
172 fmt::format("touchscreen_spec_{}", i));
173 CF_EXPECTF(android::base::WriteStringToFile(spec, spec_path,
174 true /*follow symlinks*/),
175 "Failed to write touchscreen spec to file: {}", spec_path);
176 Command touchscreen_cmd =
177 NewVhostUserInputCommand(touchscreen_sockets_[i], spec_path);
178 Command touchscreen_log_tee =
179 CF_EXPECTF(log_tee_.CreateLogTee(
180 touchscreen_cmd,
181 fmt::format("vhost_user_touchscreen_{}", i), kStdErr),
182 "Failed to create log tee for touchscreen device", i);
183 commands.emplace_back(std::move(touchscreen_cmd));
184 commands.emplace_back(std::move(touchscreen_log_tee));
185 }
186
187 std::string touchpad_template_path =
188 use_multi_touch ? DefaultMultiTouchpadSpecTemplate()
189 : DefaultSingleTouchpadSpecTemplate();
190 const std::string touchpad_template = CF_EXPECTF(
191 ReadFileContents(touchpad_template_path),
192 "Failed to load touchpad template: {}", touchpad_template_path);
193 for (int i = 0; i < instance_.touchpad_configs().size(); ++i) {
194 const int width = instance_.touchpad_configs()[i].width;
195 const int height = instance_.touchpad_configs()[i].height;
196 const std::string spec = BuildTouchSpec(
197 touchpad_template, {.index = i, .width = width, .height = height});
198 const std::string spec_path =
199 instance_.PerInstanceInternalPath(fmt::format("touchpad_spec_{}", i));
200 CF_EXPECTF(android::base::WriteStringToFile(spec, spec_path,
201 true /*follow symlinks*/),
202 "Failed to write touchpad spec to file: {}", spec_path);
203 Command touchpad_cmd =
204 NewVhostUserInputCommand(touchpad_sockets_[i], spec_path);
205 Command touchpad_log_tee = CF_EXPECTF(
206 log_tee_.CreateLogTee(
207 touchpad_cmd, fmt::format("vhost_user_touchpad_{}", i), kStdErr),
208 "Failed to create log tee for touchpad {}", i);
209 commands.emplace_back(std::move(touchpad_cmd));
210 commands.emplace_back(std::move(touchpad_log_tee));
211 }
212 return commands;
213 }
214
215 // InputConnectionsProvider
RotaryDeviceConnection() const216 SharedFD RotaryDeviceConnection() const override {
217 return rotary_sockets_.streamer_end;
218 }
219
MouseConnection() const220 SharedFD MouseConnection() const override {
221 return mouse_sockets_.streamer_end;
222 }
223
KeyboardConnection() const224 SharedFD KeyboardConnection() const override {
225 return keyboard_sockets_.streamer_end;
226 }
227
SwitchesConnection() const228 SharedFD SwitchesConnection() const override {
229 return switches_sockets_.streamer_end;
230 }
231
TouchscreenConnections() const232 std::vector<SharedFD> TouchscreenConnections() const override {
233 std::vector<SharedFD> conns;
234 conns.reserve(touchscreen_sockets_.size());
235 for (const DeviceSockets& sockets : touchscreen_sockets_) {
236 conns.emplace_back(sockets.streamer_end);
237 }
238 return conns;
239 }
240
TouchpadConnections() const241 std::vector<SharedFD> TouchpadConnections() const override {
242 std::vector<SharedFD> conns;
243 conns.reserve(touchpad_sockets_.size());
244 for (const DeviceSockets& sockets : touchpad_sockets_) {
245 conns.emplace_back(sockets.streamer_end);
246 }
247 return conns;
248 }
249
250 private:
251 // SetupFeature
Name() const252 std::string Name() const override { return "VhostInputDevices"; }
Dependencies() const253 std::unordered_set<SetupFeature*> Dependencies() const override { return {}; }
ResultSetup()254 Result<void> ResultSetup() override {
255 rotary_sockets_ =
256 CF_EXPECT(NewDeviceSockets(instance_.rotary_socket_path()),
257 "Failed to setup sockets for rotary device");
258 if (instance_.enable_mouse()) {
259 mouse_sockets_ =
260 CF_EXPECT(NewDeviceSockets(instance_.mouse_socket_path()),
261 "Failed to setup sockets for mouse device");
262 }
263 keyboard_sockets_ =
264 CF_EXPECT(NewDeviceSockets(instance_.keyboard_socket_path()),
265 "Failed to setup sockets for keyboard device");
266 switches_sockets_ =
267 CF_EXPECT(NewDeviceSockets(instance_.switches_socket_path()),
268 "Failed to setup sockets for switches device");
269 touchscreen_sockets_.reserve(instance_.display_configs().size());
270 for (int i = 0; i < instance_.display_configs().size(); ++i) {
271 touchscreen_sockets_.emplace_back(
272 CF_EXPECTF(NewDeviceSockets(instance_.touch_socket_path(i)),
273 "Failed to setup sockets for touchscreen {}", i));
274 }
275 touchpad_sockets_.reserve(instance_.touchpad_configs().size());
276 for (int i = 0; i < instance_.touchpad_configs().size(); ++i) {
277 int idx = touchscreen_sockets_.size() + i;
278 touchpad_sockets_.emplace_back(
279 CF_EXPECTF(NewDeviceSockets(instance_.touch_socket_path(idx)),
280 "Failed to setup sockets for touchpad {}", i));
281 }
282 return {};
283 }
284
285 const CuttlefishConfig::InstanceSpecific& instance_;
286 LogTeeCreator& log_tee_;
287 DeviceSockets rotary_sockets_;
288 DeviceSockets mouse_sockets_;
289 DeviceSockets keyboard_sockets_;
290 DeviceSockets switches_sockets_;
291 std::vector<DeviceSockets> touchscreen_sockets_;
292 std::vector<DeviceSockets> touchpad_sockets_;
293 };
294
295 } // namespace
296 fruit::Component<fruit::Required<const CuttlefishConfig::InstanceSpecific>,
297 InputConnectionsProvider, LogTeeCreator>
VhostInputDevicesComponent()298 VhostInputDevicesComponent() {
299 return fruit::createComponent()
300 .bind<InputConnectionsProvider, VhostInputDevices>()
301 .addMultibinding<CommandSource, VhostInputDevices>()
302 .addMultibinding<SetupFeature, VhostInputDevices>();
303 }
304
305 } // namespace cuttlefish
306