Line data Source code
1 : /*
2 : * If not stated otherwise in this file or this component's LICENSE file the
3 : * following copyright and licenses apply:
4 : *
5 : * Copyright 2022 Sky UK
6 : *
7 : * Licensed under the Apache License, Version 2.0 (the "License");
8 : * you may not use this file except in compliance with the License.
9 : * You may obtain a copy of the License at
10 : *
11 : * http://www.apache.org/licenses/LICENSE-2.0
12 : *
13 : * Unless required by applicable law or agreed to in writing, software
14 : * distributed under the License is distributed on an "AS IS" BASIS,
15 : * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
16 : * See the License for the specific language governing permissions and
17 : * limitations under the License.
18 : */
19 :
20 : #include "SessionServerApp.h"
21 : #include "LinuxUtils.h"
22 : #include "RialtoLogging.h"
23 : #include "RialtoServerManagerLogging.h"
24 : #include "SessionServerAppManager.h"
25 : #include "Utils.h"
26 : #include <algorithm>
27 : #include <chrono>
28 : #include <cstring>
29 : #include <errno.h>
30 : #include <fcntl.h>
31 : #include <signal.h>
32 : #include <string>
33 : #include <sys/socket.h>
34 : #include <sys/wait.h>
35 : #include <unistd.h>
36 : #include <utility>
37 :
38 : namespace
39 : {
40 : constexpr int kMaxPlaybackSessions{2};
41 : constexpr int kMaxWebAudioPlayers{1};
42 : const std::string kSessionManagementSocketDefaultDir{"/tmp/"};
43 : const std::string kSessionManagementSocketDefaultName{"rialto-"};
44 : const std::string kLogPathEnvVariable{"RIALTO_LOG_PATH"};
45 :
46 15 : int generateServerId()
47 : {
48 : static int id{0};
49 15 : return id++;
50 : }
51 :
52 13 : std::string generateSessionManagementSocketPath()
53 : {
54 : static int sessionNum{0};
55 13 : return kSessionManagementSocketDefaultDir + kSessionManagementSocketDefaultName + std::to_string(sessionNum++);
56 : }
57 :
58 15 : std::string getSessionManagementSocketPath(const firebolt::rialto::common::AppConfig &appConfig)
59 : {
60 : // Socket name can take the following forms:
61 : // - Empty string, in which case Rialto server will automatically allocate the socket name, e.g. "/tmp/rialto-12"
62 : // - Full path, such as "/foo/bar", in which case Rialto will use this name for the socket
63 : // - Socket name, such as "bar", in which case Rialto will create the named socket in the default dir, e.g.
64 15 : if (appConfig.clientIpcSocketName.empty())
65 : {
66 13 : return generateSessionManagementSocketPath();
67 : }
68 2 : else if (appConfig.clientIpcSocketName.at(0) == '/') // full path
69 : {
70 1 : return appConfig.clientIpcSocketName;
71 : }
72 : // Socket name
73 1 : return kSessionManagementSocketDefaultDir + appConfig.clientIpcSocketName;
74 : }
75 : } // namespace
76 :
77 : namespace rialto::servermanager::common
78 : {
79 2 : SessionServerApp::SessionServerApp(const std::shared_ptr<firebolt::rialto::wrappers::ILinuxWrapper> &linuxWrapper,
80 : const std::shared_ptr<firebolt::rialto::common::ITimerFactory> &timerFactory,
81 : ISessionServerAppManager &sessionServerAppManager,
82 : const std::list<std::string> &environmentVariables,
83 : const std::string &sessionServerPath,
84 : std::chrono::milliseconds sessionServerStartupTimeout, unsigned int socketPermissions,
85 : const std::string &socketOwner, const std::string &socketGroup,
86 2 : std::unique_ptr<firebolt::rialto::ipc::INamedSocket> &&namedSocket)
87 6 : : m_kServerId{generateServerId()}, m_initialState{firebolt::rialto::common::SessionServerState::UNINITIALIZED},
88 2 : m_socks{-1, -1}, m_linuxWrapper{linuxWrapper}, m_timerFactory{timerFactory},
89 2 : m_sessionServerAppManager{sessionServerAppManager}, m_pid{-1}, m_isPreloaded{true},
90 2 : m_kSessionServerPath{sessionServerPath}, m_kSessionServerStartupTimeout{sessionServerStartupTimeout},
91 2 : m_kSessionManagementSocketPermissions{socketPermissions}, m_kSessionManagementSocketOwner{socketOwner},
92 2 : m_kSessionManagementSocketGroup{socketGroup}, m_childInitialized{false},
93 4 : m_expectedState{firebolt::rialto::common::SessionServerState::UNINITIALIZED}, m_namedSocket{std::move(namedSocket)}
94 : {
95 2 : RIALTO_SERVER_MANAGER_LOG_INFO("Creating preloaded SessionServerApp with serverId: %d", m_kServerId);
96 2 : std::transform(environmentVariables.begin(), environmentVariables.end(), std::back_inserter(m_environmentVariables),
97 4 : [this](const std::string &str) { return strdup(addAppSuffixToLogFile(str).c_str()); });
98 2 : m_environmentVariables.push_back(nullptr);
99 : }
100 :
101 13 : SessionServerApp::SessionServerApp(const std::string &appName,
102 : const firebolt::rialto::common::SessionServerState &initialState,
103 : const firebolt::rialto::common::AppConfig &appConfig,
104 : const std::shared_ptr<firebolt::rialto::wrappers::ILinuxWrapper> &linuxWrapper,
105 : const std::shared_ptr<firebolt::rialto::common::ITimerFactory> &timerFactory,
106 : ISessionServerAppManager &sessionServerAppManager,
107 : const std::list<std::string> &environmentVariables,
108 : const std::string &sessionServerPath,
109 : std::chrono::milliseconds sessionServerStartupTimeout, unsigned int socketPermissions,
110 : const std::string &socketOwner, const std::string &socketGroup,
111 13 : std::unique_ptr<firebolt::rialto::ipc::INamedSocket> &&namedSocket)
112 13 : : m_kServerId{generateServerId()}, m_appName{appName}, m_initialState{initialState},
113 13 : m_sessionManagementSocketName{getSessionManagementSocketPath(appConfig)},
114 13 : m_clientDisplayName{appConfig.clientDisplayName}, m_socks{-1, -1}, m_linuxWrapper{linuxWrapper},
115 13 : m_timerFactory{timerFactory}, m_sessionServerAppManager{sessionServerAppManager}, m_pid{-1}, m_isPreloaded{false},
116 13 : m_kSessionServerPath{sessionServerPath}, m_kSessionServerStartupTimeout{sessionServerStartupTimeout},
117 13 : m_kSessionManagementSocketPermissions{socketPermissions}, m_kSessionManagementSocketOwner{socketOwner},
118 13 : m_kSessionManagementSocketGroup{socketGroup}, m_childInitialized{false}, m_expectedState{initialState},
119 26 : m_namedSocket{std::move(namedSocket)}
120 : {
121 13 : RIALTO_SERVER_MANAGER_LOG_INFO("Creating SessionServerApp for app: %s with appId: %d", appName.c_str(), m_kServerId);
122 13 : std::transform(environmentVariables.begin(), environmentVariables.end(), std::back_inserter(m_environmentVariables),
123 26 : [this](const std::string &str) { return strdup(addAppSuffixToLogFile(str).c_str()); });
124 13 : m_environmentVariables.push_back(nullptr);
125 13 : if (m_namedSocket)
126 : {
127 13 : m_namedSocket->bind(m_sessionManagementSocketName);
128 13 : firebolt::rialto::common::setFileOwnership(m_sessionManagementSocketName, m_kSessionManagementSocketOwner,
129 13 : m_kSessionManagementSocketGroup);
130 13 : firebolt::rialto::common::setFilePermissions(m_sessionManagementSocketName,
131 13 : m_kSessionManagementSocketPermissions);
132 : }
133 : }
134 :
135 75 : SessionServerApp::~SessionServerApp()
136 : {
137 15 : RIALTO_SERVER_MANAGER_LOG_INFO("Application %d is destructed", m_kServerId);
138 15 : cancelStartupTimerInternal();
139 15 : waitForChildProcess();
140 15 : if (m_socks[0] >= 0)
141 : {
142 2 : m_linuxWrapper->close(m_socks[0]);
143 : }
144 60 : for (char *var : m_environmentVariables)
145 : {
146 45 : if (var)
147 : {
148 30 : free(var);
149 : }
150 : }
151 15 : m_environmentVariables.clear();
152 30 : }
153 :
154 6 : bool SessionServerApp::launch()
155 : {
156 6 : RIALTO_SERVER_MANAGER_LOG_INFO("Launching: %d", m_kServerId);
157 6 : if (!initializeSockets())
158 : {
159 1 : RIALTO_SERVER_MANAGER_LOG_ERROR("Failed to launch: %d - unable to initialize sockets", m_kServerId);
160 1 : return false;
161 : }
162 5 : setupStartupTimer();
163 5 : const int kChildSocket{m_socks[0]};
164 5 : const bool kResult = spawnSessionServer();
165 5 : std::unique_lock<std::mutex> lock{m_processStartupMutex};
166 12 : if (!m_processStartupCv.wait_for(lock, std::chrono::seconds{1}, [this]() { return m_childInitialized; }))
167 : {
168 2 : RIALTO_SERVER_MANAGER_LOG_ERROR("Child initialization failed. Timeout on waiting for process startup");
169 2 : return false;
170 : }
171 3 : RIALTO_SERVER_MANAGER_LOG_DEBUG("Child initialized. Parent process will close the socket: %d now.", kChildSocket);
172 3 : if (0 != m_linuxWrapper->close(kChildSocket))
173 : {
174 3 : RIALTO_SERVER_MANAGER_LOG_SYS_ERROR(errno, "Close of socket %d failed in parent process", kChildSocket);
175 : }
176 3 : m_socks[0] = -1;
177 3 : return kResult;
178 5 : }
179 :
180 15 : bool SessionServerApp::isPreloaded() const
181 : {
182 15 : return m_isPreloaded;
183 : }
184 :
185 4 : bool SessionServerApp::configure(const std::string &appName,
186 : const firebolt::rialto::common::SessionServerState &initialState,
187 : const firebolt::rialto::common::AppConfig &appConfig)
188 : {
189 4 : if (!m_isPreloaded)
190 : {
191 2 : RIALTO_SERVER_MANAGER_LOG_ERROR("SessionServerApp is already configured!");
192 2 : return false;
193 : }
194 2 : m_appName = appName;
195 2 : m_initialState = initialState;
196 2 : m_sessionManagementSocketName = getSessionManagementSocketPath(appConfig);
197 2 : m_clientDisplayName = appConfig.clientDisplayName;
198 2 : m_isPreloaded = false;
199 2 : m_expectedState = initialState;
200 2 : if (m_namedSocket)
201 : {
202 2 : m_namedSocket->bind(m_sessionManagementSocketName);
203 2 : firebolt::rialto::common::setFileOwnership(m_sessionManagementSocketName, m_kSessionManagementSocketOwner,
204 2 : m_kSessionManagementSocketGroup);
205 2 : firebolt::rialto::common::setFilePermissions(m_sessionManagementSocketName,
206 2 : m_kSessionManagementSocketPermissions);
207 : }
208 2 : return true;
209 : }
210 :
211 2 : bool SessionServerApp::isConnected() const
212 : {
213 2 : std::unique_lock<std::mutex> lock{m_timerMutex};
214 4 : return !m_startupTimer || !m_startupTimer->isActive();
215 2 : }
216 :
217 3 : std::string SessionServerApp::getSessionManagementSocketName() const
218 : {
219 3 : return m_sessionManagementSocketName;
220 : }
221 :
222 15 : unsigned int SessionServerApp::getSessionManagementSocketPermissions() const
223 : {
224 15 : return m_kSessionManagementSocketPermissions;
225 : }
226 :
227 11 : std::string SessionServerApp::getSessionManagementSocketOwner() const
228 : {
229 11 : return m_kSessionManagementSocketOwner;
230 : }
231 :
232 11 : std::string SessionServerApp::getSessionManagementSocketGroup() const
233 : {
234 11 : return m_kSessionManagementSocketGroup;
235 : }
236 :
237 6 : std::string SessionServerApp::getClientDisplayName() const
238 : {
239 6 : return m_clientDisplayName;
240 : }
241 :
242 19 : firebolt::rialto::common::SessionServerState SessionServerApp::getInitialState() const
243 : {
244 19 : return m_initialState;
245 : }
246 :
247 1 : int SessionServerApp::getServerId() const
248 : {
249 1 : return m_kServerId;
250 : }
251 :
252 15 : const std::string &SessionServerApp::getAppName() const
253 : {
254 15 : return m_appName;
255 : }
256 :
257 15 : int SessionServerApp::getAppManagementSocketName() const
258 : {
259 15 : return m_socks[1];
260 : }
261 :
262 15 : int SessionServerApp::getMaxPlaybackSessions() const
263 : {
264 15 : return kMaxPlaybackSessions; // temporarily hardcoded
265 : }
266 :
267 15 : int SessionServerApp::getMaxWebAudioPlayers() const
268 : {
269 15 : return kMaxWebAudioPlayers; // temporarily hardcoded
270 : }
271 :
272 1 : void SessionServerApp::cancelStartupTimer()
273 : {
274 1 : cancelStartupTimerInternal();
275 : }
276 :
277 16 : void SessionServerApp::cancelStartupTimerInternal()
278 : {
279 16 : std::unique_lock<std::mutex> lock{m_timerMutex};
280 16 : if (m_startupTimer && m_startupTimer->isActive())
281 : {
282 3 : RIALTO_SERVER_MANAGER_LOG_INFO("Application: %d connected successfully", m_kServerId);
283 3 : m_startupTimer->cancel();
284 : }
285 16 : }
286 :
287 30 : std::string SessionServerApp::addAppSuffixToLogFile(const std::string &envVar) const
288 : {
289 30 : if (envVar.find(kLogPathEnvVariable) != std::string::npos)
290 : {
291 13 : return envVar + "." + std::to_string(m_kServerId);
292 : }
293 17 : return envVar;
294 : }
295 :
296 1 : void SessionServerApp::kill()
297 : {
298 1 : if (m_pid > 0)
299 : {
300 1 : m_linuxWrapper->kill(m_pid, SIGKILL);
301 1 : m_pid = -1;
302 : }
303 : }
304 :
305 1 : void SessionServerApp::setExpectedState(const firebolt::rialto::common::SessionServerState &state)
306 : {
307 1 : m_expectedState = state;
308 : }
309 :
310 1 : firebolt::rialto::common::SessionServerState SessionServerApp::getExpectedState() const
311 : {
312 1 : return m_expectedState;
313 : }
314 :
315 6 : bool SessionServerApp::initializeSockets()
316 : {
317 12 : if (m_linuxWrapper->socketpair(AF_UNIX, SOCK_SEQPACKET | SOCK_CLOEXEC | SOCK_NONBLOCK, 0, m_socks.data()) < 0)
318 : {
319 1 : RIALTO_SERVER_MANAGER_LOG_SYS_ERROR(errno, "socketpair failed");
320 1 : return false;
321 : }
322 5 : return true;
323 : }
324 :
325 5 : void SessionServerApp::setupStartupTimer()
326 : {
327 5 : if (std::chrono::milliseconds(0) < m_kSessionServerStartupTimeout)
328 : {
329 4 : std::unique_lock<std::mutex> lock{m_timerMutex};
330 8 : m_startupTimer = m_timerFactory->createTimer(m_kSessionServerStartupTimeout, [this]()
331 5 : { m_sessionServerAppManager.onServerStartupTimeout(m_kServerId); });
332 4 : }
333 : else
334 : {
335 1 : RIALTO_SERVER_MANAGER_LOG_INFO("Startup timer disabled");
336 : }
337 5 : }
338 :
339 5 : bool SessionServerApp::spawnSessionServer()
340 : {
341 15 : return m_linuxWrapper->vfork(
342 10 : [this](pid_t childPid)
343 : {
344 5 : if (childPid == -1)
345 : {
346 1 : RIALTO_SERVER_MANAGER_LOG_SYS_ERROR(errno, "Unable to spawn RialtoSessionServer - fork problem");
347 1 : m_linuxWrapper->close(m_socks[1]);
348 1 : m_socks[1] = -1;
349 1 : return false;
350 : }
351 4 : else if (childPid > 0)
352 : {
353 1 : RIALTO_SERVER_MANAGER_LOG_DEBUG("%d launched. PID: %d", m_kServerId, childPid);
354 1 : m_pid = childPid;
355 1 : return true;
356 : }
357 : else
358 : {
359 3 : int newSocket{-1};
360 : {
361 3 : std::unique_lock<std::mutex> lock{m_processStartupMutex};
362 3 : newSocket = m_linuxWrapper->dup(m_socks[0]);
363 3 : if (0 != m_linuxWrapper->close(m_socks[0]))
364 : {
365 3 : RIALTO_SERVER_MANAGER_LOG_SYS_WARN(errno, "Socket %d could not be closed in child process.",
366 : m_socks[0]);
367 : }
368 3 : RIALTO_SERVER_MANAGER_LOG_DEBUG("Child socket initialized: %d", newSocket);
369 3 : m_childInitialized = true;
370 3 : m_processStartupCv.notify_one();
371 : }
372 3 : if (!firebolt::rialto::logging::isConsoleLoggingEnabled())
373 : {
374 0 : int devNull = m_linuxWrapper->open("/dev/null", O_RDWR, 0);
375 0 : if (devNull < 0)
376 : {
377 0 : m_linuxWrapper->exit(EXIT_FAILURE);
378 0 : return false; // wrapper function is not [[noreturn]]
379 : }
380 0 : m_linuxWrapper->dup2(devNull, STDIN_FILENO);
381 0 : m_linuxWrapper->dup2(devNull, STDOUT_FILENO);
382 0 : m_linuxWrapper->dup2(devNull, STDERR_FILENO);
383 0 : if (devNull > STDERR_FILENO)
384 : {
385 0 : m_linuxWrapper->close(devNull);
386 0 : devNull = -1;
387 : }
388 : }
389 3 : const std::string kAppMgmtSocketStr{std::to_string(newSocket)};
390 3 : char *const appArguments[] = {strdup(m_kSessionServerPath.c_str()), strdup(kAppMgmtSocketStr.c_str()),
391 3 : nullptr};
392 3 : RIALTO_SERVER_MANAGER_LOG_DEBUG("PID: %d, executing: \"%s\" \"%s\"", m_linuxWrapper->getpid(),
393 : appArguments[0], appArguments[1]);
394 3 : m_linuxWrapper->execve(m_kSessionServerPath.c_str(), appArguments, m_environmentVariables.data());
395 3 : RIALTO_SERVER_MANAGER_LOG_SYS_ERROR(errno, "Unable to spawn RialtoSessionServer - execve problem");
396 12 : for (char *arg : appArguments)
397 : {
398 9 : free(arg);
399 : }
400 3 : m_linuxWrapper->exit(EXIT_FAILURE);
401 3 : return true; // wrapper function is not [[noreturn]]
402 : }
403 10 : });
404 : }
405 :
406 15 : void SessionServerApp::waitForChildProcess()
407 : {
408 15 : if (m_pid == -1)
409 : {
410 14 : return;
411 : }
412 : auto killTimer =
413 2 : m_timerFactory->createTimer(std::chrono::milliseconds{1500},
414 0 : [this]()
415 : {
416 1 : RIALTO_SERVER_MANAGER_LOG_ERROR("Waitpid timeout. Killing: %d", m_kServerId);
417 1 : kill();
418 2 : });
419 1 : if (m_linuxWrapper->waitpid(m_pid, nullptr, 0) < 0)
420 : {
421 1 : RIALTO_SERVER_MANAGER_LOG_SYS_WARN(errno, "waitpid failed for %d", m_kServerId);
422 : }
423 1 : killTimer->cancel();
424 1 : RIALTO_SERVER_MANAGER_LOG_DEBUG("Server with id: %d exited.", m_kServerId);
425 : }
426 :
427 10 : bool SessionServerApp::isNamedSocketInitialized() const
428 : {
429 10 : return m_namedSocket != nullptr;
430 : }
431 :
432 2 : int SessionServerApp::getSessionManagementSocketFd() const
433 : {
434 2 : if (m_namedSocket)
435 : {
436 1 : return m_namedSocket->getFd();
437 : }
438 1 : return -1;
439 : }
440 :
441 1 : std::unique_ptr<firebolt::rialto::ipc::INamedSocket> &&SessionServerApp::releaseNamedSocket()
442 : {
443 1 : if (m_namedSocket)
444 : {
445 1 : m_namedSocket->blockNewConnections();
446 : }
447 1 : return std::move(m_namedSocket);
448 : }
449 : } // namespace rialto::servermanager::common
|