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