xref: /openbmc/bmcweb/include/obmc_console.hpp (revision faf100f963c9cd8c81c277ad6bc188eecd0fc12b)
1 #pragma once
2 #include "app.hpp"
3 #include "async_resp.hpp"
4 #include "websocket.hpp"
5 
6 #include <sys/socket.h>
7 
8 #include <boost/asio/local/stream_protocol.hpp>
9 #include <boost/container/flat_set.hpp>
10 
11 namespace crow
12 {
13 namespace obmc_console
14 {
15 
16 // NOLINTNEXTLINE(cppcoreguidelines-avoid-non-const-global-variables)
17 static std::unique_ptr<boost::asio::local::stream_protocol::socket> hostSocket;
18 
19 // NOLINTNEXTLINE(cppcoreguidelines-avoid-non-const-global-variables)
20 static std::array<char, 4096> outputBuffer;
21 // NOLINTNEXTLINE(cppcoreguidelines-avoid-non-const-global-variables)
22 static std::string inputBuffer;
23 
24 // NOLINTNEXTLINE(cppcoreguidelines-avoid-non-const-global-variables)
25 static boost::container::flat_set<crow::websocket::Connection*> sessions;
26 
27 // NOLINTNEXTLINE(cppcoreguidelines-avoid-non-const-global-variables)
28 static bool doingWrite = false;
29 
30 inline void doWrite()
31 {
32     if (doingWrite)
33     {
34         BMCWEB_LOG_DEBUG << "Already writing.  Bailing out";
35         return;
36     }
37 
38     if (inputBuffer.empty())
39     {
40         BMCWEB_LOG_DEBUG << "Outbuffer empty.  Bailing out";
41         return;
42     }
43 
44     if (!hostSocket)
45     {
46         BMCWEB_LOG_ERROR << "doWrite(): Socket closed.";
47         return;
48     }
49 
50     doingWrite = true;
51     hostSocket->async_write_some(
52         boost::asio::buffer(inputBuffer.data(), inputBuffer.size()),
53         [](const boost::beast::error_code& ec, std::size_t bytesWritten) {
54         doingWrite = false;
55         inputBuffer.erase(0, bytesWritten);
56 
57         if (ec == boost::asio::error::eof)
58         {
59             for (crow::websocket::Connection* session : sessions)
60             {
61                 session->close("Error in reading to host port");
62             }
63             return;
64         }
65         if (ec)
66         {
67             BMCWEB_LOG_ERROR << "Error in host serial write " << ec.message();
68             return;
69         }
70         doWrite();
71         });
72 }
73 
74 inline void doRead()
75 {
76     if (!hostSocket)
77     {
78         BMCWEB_LOG_ERROR << "doRead(): Socket closed.";
79         return;
80     }
81 
82     BMCWEB_LOG_DEBUG << "Reading from socket";
83     hostSocket->async_read_some(
84         boost::asio::buffer(outputBuffer.data(), outputBuffer.size()),
85         [](const boost::system::error_code& ec, std::size_t bytesRead) {
86         BMCWEB_LOG_DEBUG << "read done.  Read " << bytesRead << " bytes";
87         if (ec)
88         {
89             BMCWEB_LOG_ERROR << "Couldn't read from host serial port: "
90                              << ec.message();
91             for (crow::websocket::Connection* session : sessions)
92             {
93                 session->close("Error in connecting to host port");
94             }
95             return;
96         }
97         std::string_view payload(outputBuffer.data(), bytesRead);
98         for (crow::websocket::Connection* session : sessions)
99         {
100             session->sendBinary(payload);
101         }
102         doRead();
103         });
104 }
105 
106 // If connection is active then remove it from the connection map
107 inline bool removeConnection(crow::websocket::Connection& conn)
108 {
109     bool ret = false;
110 
111     if (sessions.erase(&conn) != 0U)
112     {
113         ret = true;
114     }
115 
116     if (sessions.empty())
117     {
118         hostSocket = nullptr;
119         inputBuffer.clear();
120         inputBuffer.shrink_to_fit();
121     }
122     return ret;
123 }
124 
125 inline void connectConsoleSocket(crow::websocket::Connection& conn,
126                                  const boost::system::error_code& ec,
127                                  const sdbusplus::message::unix_fd& unixfd)
128 {
129     int fd = -1;
130 
131     if (ec)
132     {
133         BMCWEB_LOG_ERROR << "Failed to call console Connect() method"
134                          << " DBUS error: " << ec.message();
135         if (removeConnection(conn))
136         {
137             conn.close("Failed to call console Connect() method");
138         }
139         return;
140     }
141 
142     // Make sure that connection is still open.
143     if (!sessions.contains(&conn))
144     {
145         return;
146     }
147 
148     fd = dup(unixfd);
149     if (fd == -1)
150     {
151         BMCWEB_LOG_ERROR << "Failed to dup the DBUS unixfd"
152                          << " error: " << strerror(errno);
153         if (removeConnection(conn))
154         {
155             conn.close("Failed to dup the DBUS unixfd");
156         }
157         return;
158     }
159 
160     BMCWEB_LOG_DEBUG << "Console web socket path: " << conn.req.target()
161                      << " Console unix FD: " << unixfd << " duped FD: " << fd;
162 
163     if (hostSocket == nullptr)
164     {
165         boost::system::error_code ec1;
166         boost::asio::local::stream_protocol proto;
167         hostSocket =
168             std::make_unique<boost::asio::local::stream_protocol::socket>(
169                 conn.getIoContext());
170 
171         hostSocket->assign(proto, fd, ec1);
172 
173         if (ec1)
174         {
175             close(fd);
176             BMCWEB_LOG_ERROR << "Failed to assign the DBUS socket"
177                              << " Socket assign error: " << ec1.message();
178             if (removeConnection(conn))
179             {
180                 conn.close("Failed to assign the DBUS socket");
181             }
182         }
183         else
184         {
185             conn.resumeRead();
186             doWrite();
187             doRead();
188         }
189     }
190     else
191     {
192         BMCWEB_LOG_DEBUG << "Socket already exist so close the new fd: " << fd;
193         close(fd);
194     }
195 }
196 
197 // Query consoles from DBUS and find the matching to the
198 // rules string.
199 inline void onOpen(crow::websocket::Connection& conn)
200 {
201     BMCWEB_LOG_DEBUG << "Connection " << &conn << " opened";
202 
203     // Save the connection in the map
204     sessions.insert(&conn);
205 
206     // We need to wait for dbus and the websockets to hook up before data is
207     // sent/received.  Tell the core to hold off messages until the sockets are
208     // up
209     if (hostSocket == nullptr)
210     {
211         conn.deferRead();
212     }
213 
214     // The console id 'default' is used for the console0
215     // We need to change it when we provide full multi-console support.
216     const std::string consolePath = "/xyz/openbmc_project/console/default";
217     const std::string consoleService = "xyz.openbmc_project.Console.default";
218 
219     BMCWEB_LOG_DEBUG << "Console Object path = " << consolePath
220                      << " service = " << consoleService
221                      << " Request target = " << conn.req.target();
222 
223     // Call Connect() method to get the unix FD
224     crow::connections::systemBus->async_method_call(
225         [&conn](const boost::system::error_code& ec,
226                 const sdbusplus::message::unix_fd& unixfd) {
227         connectConsoleSocket(conn, ec, unixfd);
228         },
229         consoleService, consolePath, "xyz.openbmc_project.Console.Access",
230         "Connect");
231 }
232 
233 inline void requestRoutes(App& app)
234 {
235     BMCWEB_ROUTE(app, "/console0")
236         .privileges({{"OpenBMCHostConsole"}})
237         .websocket()
238         .onopen(onOpen)
239         .onclose([](crow::websocket::Connection& conn,
240                     [[maybe_unused]] const std::string& reason) {
241             BMCWEB_LOG_INFO << "Closing websocket. Reason: " << reason;
242 
243             removeConnection(conn);
244         })
245         .onmessage([]([[maybe_unused]] crow::websocket::Connection& conn,
246                       const std::string& data, [[maybe_unused]] bool isBinary) {
247             inputBuffer += data;
248             doWrite();
249         });
250 }
251 } // namespace obmc_console
252 } // namespace crow
253