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 16 : int generateServerId()
47 : {
48 : static int id{0};
49 16 : return id++;
50 : }
51 :
52 14 : std::string generateSessionManagementSocketPath()
53 : {
54 : static int sessionNum{0};
55 14 : return kSessionManagementSocketDefaultDir + kSessionManagementSocketDefaultName + std::to_string(sessionNum++);
56 : }
57 :
58 16 : 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 16 : if (appConfig.clientIpcSocketName.empty())
65 : {
66 14 : 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 2 : const std::string &socketOwner, const std::string &socketGroup)
86 6 : : m_kServerId{generateServerId()}, m_initialState{firebolt::rialto::common::SessionServerState::UNINITIALIZED},
87 2 : m_socks{-1, -1}, m_linuxWrapper{linuxWrapper}, m_timerFactory{timerFactory},
88 2 : m_sessionServerAppManager{sessionServerAppManager}, m_pid{-1}, m_isPreloaded{true},
89 2 : m_kSessionServerPath{sessionServerPath}, m_kSessionServerStartupTimeout{sessionServerStartupTimeout},
90 2 : m_kSessionManagementSocketPermissions{socketPermissions}, m_kSessionManagementSocketOwner{socketOwner},
91 2 : m_kSessionManagementSocketGroup{socketGroup}, m_childInitialized{false},
92 4 : m_expectedState{firebolt::rialto::common::SessionServerState::UNINITIALIZED}
93 : {
94 2 : RIALTO_SERVER_MANAGER_LOG_INFO("Creating preloaded SessionServerApp with serverId: %d", m_kServerId);
95 2 : std::transform(environmentVariables.begin(), environmentVariables.end(), std::back_inserter(m_environmentVariables),
96 4 : [this](const std::string &str) { return strdup(addAppSuffixToLogFile(str).c_str()); });
97 2 : m_environmentVariables.push_back(nullptr);
98 : }
99 :
100 14 : SessionServerApp::SessionServerApp(const std::string &appName,
101 : const firebolt::rialto::common::SessionServerState &initialState,
102 : const firebolt::rialto::common::AppConfig &appConfig,
103 : const std::shared_ptr<firebolt::rialto::wrappers::ILinuxWrapper> &linuxWrapper,
104 : const std::shared_ptr<firebolt::rialto::common::ITimerFactory> &timerFactory,
105 : ISessionServerAppManager &sessionServerAppManager,
106 : const std::list<std::string> &environmentVariables,
107 : const std::string &sessionServerPath,
108 : std::chrono::milliseconds sessionServerStartupTimeout, unsigned int socketPermissions,
109 : const std::string &socketOwner, const std::string &socketGroup,
110 14 : std::unique_ptr<firebolt::rialto::ipc::INamedSocket> &&namedSocket)
111 14 : : m_kServerId{generateServerId()}, m_appName{appName}, m_initialState{initialState},
112 14 : m_sessionManagementSocketName{getSessionManagementSocketPath(appConfig)},
113 14 : m_clientDisplayName{appConfig.clientDisplayName}, m_socks{-1, -1}, m_linuxWrapper{linuxWrapper},
114 14 : m_timerFactory{timerFactory}, m_sessionServerAppManager{sessionServerAppManager}, m_pid{-1}, m_isPreloaded{false},
115 14 : m_kSessionServerPath{sessionServerPath}, m_kSessionServerStartupTimeout{sessionServerStartupTimeout},
116 14 : m_kSessionManagementSocketPermissions{socketPermissions}, m_kSessionManagementSocketOwner{socketOwner},
117 14 : m_kSessionManagementSocketGroup{socketGroup}, m_childInitialized{false}, m_expectedState{initialState},
118 28 : m_namedSocket{std::move(namedSocket)}
119 : {
120 14 : RIALTO_SERVER_MANAGER_LOG_INFO("Creating SessionServerApp for app: %s with appId: %d", appName.c_str(), m_kServerId);
121 14 : std::transform(environmentVariables.begin(), environmentVariables.end(), std::back_inserter(m_environmentVariables),
122 28 : [this](const std::string &str) { return strdup(addAppSuffixToLogFile(str).c_str()); });
123 14 : m_environmentVariables.push_back(nullptr);
124 14 : if (m_namedSocket)
125 : {
126 14 : m_namedSocket->bind(m_sessionManagementSocketName);
127 14 : firebolt::rialto::common::setFileOwnership(m_sessionManagementSocketName, m_kSessionManagementSocketOwner,
128 14 : m_kSessionManagementSocketGroup);
129 14 : firebolt::rialto::common::setFilePermissions(m_sessionManagementSocketName,
130 14 : m_kSessionManagementSocketPermissions);
131 : }
132 : }
133 :
134 64 : SessionServerApp::~SessionServerApp()
135 : {
136 16 : RIALTO_SERVER_MANAGER_LOG_INFO("Application %d is destructed", m_kServerId);
137 16 : doCleanup();
138 64 : for (char *var : m_environmentVariables)
139 : {
140 48 : if (var)
141 : {
142 32 : free(var);
143 : }
144 : }
145 16 : m_environmentVariables.clear();
146 : }
147 :
148 7 : bool SessionServerApp::launch()
149 : {
150 7 : RIALTO_SERVER_MANAGER_LOG_INFO("Launching: %d", m_kServerId);
151 7 : if (!initializeSockets())
152 : {
153 1 : RIALTO_SERVER_MANAGER_LOG_ERROR("Failed to launch: %d - unable to initialize sockets", m_kServerId);
154 1 : return false;
155 : }
156 6 : setupStartupTimer();
157 6 : const int kChildSocket{m_socks[0]};
158 6 : const bool kResult = spawnSessionServer();
159 6 : std::unique_lock<std::mutex> lock{m_processStartupMutex};
160 14 : if (!m_processStartupCv.wait_for(lock, std::chrono::seconds{1}, [this]() { return m_childInitialized; }))
161 : {
162 2 : RIALTO_SERVER_MANAGER_LOG_ERROR("Child initialization failed. Timeout on waiting for process startup");
163 2 : return false;
164 : }
165 4 : RIALTO_SERVER_MANAGER_LOG_DEBUG("Child initialized. Parent process will close the socket: %d now.", kChildSocket);
166 4 : if (0 != m_linuxWrapper->close(kChildSocket))
167 : {
168 4 : RIALTO_SERVER_MANAGER_LOG_SYS_ERROR(errno, "Close of socket %d failed in parent process", kChildSocket);
169 : }
170 4 : m_socks[0] = -1;
171 4 : return kResult;
172 6 : }
173 :
174 16 : bool SessionServerApp::isPreloaded() const
175 : {
176 16 : return m_isPreloaded;
177 : }
178 :
179 4 : bool SessionServerApp::configure(const std::string &appName,
180 : const firebolt::rialto::common::SessionServerState &initialState,
181 : const firebolt::rialto::common::AppConfig &appConfig,
182 : std::unique_ptr<firebolt::rialto::ipc::INamedSocket> &&namedSocket)
183 : {
184 4 : if (!m_isPreloaded)
185 : {
186 2 : RIALTO_SERVER_MANAGER_LOG_ERROR("SessionServerApp is already configured!");
187 2 : return false;
188 : }
189 2 : m_namedSocket = std::move(namedSocket);
190 2 : m_appName = appName;
191 2 : m_initialState = initialState;
192 2 : m_sessionManagementSocketName = getSessionManagementSocketPath(appConfig);
193 2 : m_clientDisplayName = appConfig.clientDisplayName;
194 2 : m_isPreloaded = false;
195 2 : m_expectedState = initialState;
196 2 : if (m_namedSocket)
197 : {
198 2 : m_namedSocket->bind(m_sessionManagementSocketName);
199 2 : firebolt::rialto::common::setFileOwnership(m_sessionManagementSocketName, m_kSessionManagementSocketOwner,
200 2 : m_kSessionManagementSocketGroup);
201 2 : firebolt::rialto::common::setFilePermissions(m_sessionManagementSocketName,
202 2 : m_kSessionManagementSocketPermissions);
203 : }
204 2 : return true;
205 : }
206 :
207 2 : bool SessionServerApp::isConnected() const
208 : {
209 2 : std::unique_lock<std::mutex> lock{m_timerMutex};
210 4 : return !m_startupTimer || !m_startupTimer->isActive();
211 2 : }
212 :
213 3 : std::string SessionServerApp::getSessionManagementSocketName() const
214 : {
215 3 : return m_sessionManagementSocketName;
216 : }
217 :
218 16 : unsigned int SessionServerApp::getSessionManagementSocketPermissions() const
219 : {
220 16 : return m_kSessionManagementSocketPermissions;
221 : }
222 :
223 12 : std::string SessionServerApp::getSessionManagementSocketOwner() const
224 : {
225 12 : return m_kSessionManagementSocketOwner;
226 : }
227 :
228 12 : std::string SessionServerApp::getSessionManagementSocketGroup() const
229 : {
230 12 : return m_kSessionManagementSocketGroup;
231 : }
232 :
233 6 : std::string SessionServerApp::getClientDisplayName() const
234 : {
235 6 : return m_clientDisplayName;
236 : }
237 :
238 20 : firebolt::rialto::common::SessionServerState SessionServerApp::getInitialState() const
239 : {
240 20 : return m_initialState;
241 : }
242 :
243 1 : int SessionServerApp::getServerId() const
244 : {
245 1 : return m_kServerId;
246 : }
247 :
248 16 : const std::string &SessionServerApp::getAppName() const
249 : {
250 16 : return m_appName;
251 : }
252 :
253 16 : int SessionServerApp::getAppManagementSocketName() const
254 : {
255 16 : return m_socks[1];
256 : }
257 :
258 16 : int SessionServerApp::getMaxPlaybackSessions() const
259 : {
260 16 : return kMaxPlaybackSessions; // temporarily hardcoded
261 : }
262 :
263 16 : int SessionServerApp::getMaxWebAudioPlayers() const
264 : {
265 16 : return kMaxWebAudioPlayers; // temporarily hardcoded
266 : }
267 :
268 1 : void SessionServerApp::cancelStartupTimer()
269 : {
270 1 : cancelStartupTimerInternal();
271 : }
272 :
273 18 : void SessionServerApp::cancelStartupTimerInternal()
274 : {
275 18 : std::unique_lock<std::mutex> lock{m_timerMutex};
276 18 : if (m_startupTimer && m_startupTimer->isActive())
277 : {
278 4 : RIALTO_SERVER_MANAGER_LOG_INFO("Application: %d connected successfully", m_kServerId);
279 4 : m_startupTimer->cancel();
280 : }
281 18 : }
282 :
283 32 : std::string SessionServerApp::addAppSuffixToLogFile(const std::string &envVar) const
284 : {
285 32 : if (envVar.find(kLogPathEnvVariable) != std::string::npos)
286 : {
287 14 : return envVar + "." + std::to_string(m_kServerId);
288 : }
289 18 : return envVar;
290 : }
291 :
292 1 : void SessionServerApp::kill()
293 : {
294 1 : if (m_pid > 0)
295 : {
296 1 : m_linuxWrapper->kill(m_pid, SIGKILL);
297 : }
298 : }
299 :
300 1 : void SessionServerApp::setExpectedState(const firebolt::rialto::common::SessionServerState &state)
301 : {
302 1 : m_expectedState = state;
303 : }
304 :
305 1 : firebolt::rialto::common::SessionServerState SessionServerApp::getExpectedState() const
306 : {
307 1 : return m_expectedState;
308 : }
309 :
310 7 : bool SessionServerApp::initializeSockets()
311 : {
312 14 : if (m_linuxWrapper->socketpair(AF_UNIX, SOCK_SEQPACKET | SOCK_CLOEXEC | SOCK_NONBLOCK, 0, m_socks.data()) < 0)
313 : {
314 1 : RIALTO_SERVER_MANAGER_LOG_SYS_ERROR(errno, "socketpair failed");
315 1 : return false;
316 : }
317 6 : return true;
318 : }
319 :
320 6 : void SessionServerApp::setupStartupTimer()
321 : {
322 6 : if (std::chrono::milliseconds(0) < m_kSessionServerStartupTimeout)
323 : {
324 5 : std::unique_lock<std::mutex> lock{m_timerMutex};
325 10 : m_startupTimer = m_timerFactory->createTimer(m_kSessionServerStartupTimeout, [this]()
326 6 : { m_sessionServerAppManager.onServerStartupTimeout(m_kServerId); });
327 5 : }
328 : else
329 : {
330 1 : RIALTO_SERVER_MANAGER_LOG_INFO("Startup timer disabled");
331 : }
332 6 : }
333 :
334 6 : bool SessionServerApp::spawnSessionServer()
335 : {
336 18 : return m_linuxWrapper->vfork(
337 12 : [this](pid_t childPid)
338 : {
339 6 : if (childPid == -1)
340 : {
341 1 : RIALTO_SERVER_MANAGER_LOG_SYS_ERROR(errno, "Unable to spawn RialtoSessionServer - fork problem");
342 1 : m_linuxWrapper->close(m_socks[1]);
343 1 : m_socks[1] = -1;
344 1 : return false;
345 : }
346 5 : else if (childPid > 0)
347 : {
348 1 : RIALTO_SERVER_MANAGER_LOG_DEBUG("%d launched. PID: %d", m_kServerId, childPid);
349 1 : m_pid = childPid;
350 1 : return true;
351 : }
352 : else
353 : {
354 4 : int newSocket{-1};
355 : {
356 4 : std::unique_lock<std::mutex> lock{m_processStartupMutex};
357 4 : newSocket = m_linuxWrapper->dup(m_socks[0]);
358 4 : if (0 != m_linuxWrapper->close(m_socks[0]))
359 : {
360 4 : RIALTO_SERVER_MANAGER_LOG_SYS_WARN(errno, "Socket %d could not be closed in child process.",
361 : m_socks[0]);
362 : }
363 4 : RIALTO_SERVER_MANAGER_LOG_DEBUG("Child socket initialized: %d", newSocket);
364 4 : m_childInitialized = true;
365 4 : m_processStartupCv.notify_one();
366 : }
367 4 : if (!firebolt::rialto::logging::isConsoleLoggingEnabled())
368 : {
369 0 : int devNull = m_linuxWrapper->open("/dev/null", O_RDWR, 0);
370 0 : if (devNull < 0)
371 : {
372 0 : m_linuxWrapper->exit(EXIT_FAILURE);
373 0 : return false; // wrapper function is not [[noreturn]]
374 : }
375 0 : m_linuxWrapper->dup2(devNull, STDIN_FILENO);
376 0 : m_linuxWrapper->dup2(devNull, STDOUT_FILENO);
377 0 : m_linuxWrapper->dup2(devNull, STDERR_FILENO);
378 0 : if (devNull > STDERR_FILENO)
379 : {
380 0 : m_linuxWrapper->close(devNull);
381 0 : devNull = -1;
382 : }
383 : }
384 4 : const std::string kAppMgmtSocketStr{std::to_string(newSocket)};
385 4 : char *const appArguments[] = {strdup(m_kSessionServerPath.c_str()), strdup(kAppMgmtSocketStr.c_str()),
386 4 : nullptr};
387 4 : RIALTO_SERVER_MANAGER_LOG_DEBUG("PID: %d, executing: \"%s\" \"%s\"", m_linuxWrapper->getpid(),
388 : appArguments[0], appArguments[1]);
389 4 : m_linuxWrapper->execve(m_kSessionServerPath.c_str(), appArguments, m_environmentVariables.data());
390 4 : RIALTO_SERVER_MANAGER_LOG_SYS_ERROR(errno, "Unable to spawn RialtoSessionServer - execve problem");
391 16 : for (char *arg : appArguments)
392 : {
393 12 : free(arg);
394 : }
395 4 : m_linuxWrapper->exit(EXIT_FAILURE);
396 4 : return true; // wrapper function is not [[noreturn]]
397 : }
398 12 : });
399 : }
400 :
401 17 : void SessionServerApp::waitForChildProcess()
402 : {
403 17 : if (m_pid == -1)
404 : {
405 16 : return;
406 : }
407 : auto killTimer =
408 2 : m_timerFactory->createTimer(std::chrono::milliseconds{1500},
409 0 : [this]()
410 : {
411 1 : RIALTO_SERVER_MANAGER_LOG_ERROR("Waitpid timeout. Killing: %d", m_kServerId);
412 1 : kill();
413 2 : });
414 1 : if (m_linuxWrapper->waitpid(m_pid, nullptr, 0) < 0)
415 : {
416 1 : RIALTO_SERVER_MANAGER_LOG_SYS_WARN(errno, "waitpid failed for %d", m_kServerId);
417 : }
418 1 : killTimer->cancel();
419 1 : m_pid = -1;
420 1 : RIALTO_SERVER_MANAGER_LOG_DEBUG("Server with id: %d exited.", m_kServerId);
421 : }
422 :
423 11 : bool SessionServerApp::isNamedSocketInitialized() const
424 : {
425 11 : return m_namedSocket != nullptr;
426 : }
427 :
428 2 : int SessionServerApp::getSessionManagementSocketFd() const
429 : {
430 2 : if (m_namedSocket)
431 : {
432 1 : return m_namedSocket->getFd();
433 : }
434 1 : return -1;
435 : }
436 :
437 1 : std::unique_ptr<firebolt::rialto::ipc::INamedSocket> &&SessionServerApp::releaseNamedSocket()
438 : {
439 1 : if (m_namedSocket)
440 : {
441 1 : m_namedSocket->blockNewConnections();
442 : }
443 1 : return std::move(m_namedSocket);
444 : }
445 :
446 1 : void SessionServerApp::cleanup()
447 : {
448 1 : doCleanup();
449 : }
450 :
451 17 : void SessionServerApp::doCleanup()
452 : {
453 17 : cancelStartupTimerInternal();
454 17 : waitForChildProcess();
455 17 : if (m_socks[0] >= 0)
456 : {
457 2 : m_linuxWrapper->close(m_socks[0]);
458 : }
459 17 : }
460 : } // namespace rialto::servermanager::common
|