LCOV - code coverage report
Current view: top level - serverManager/common/source - SessionServerApp.cpp (source / functions) Coverage Total Hit
Test: coverage.info Lines: 95.1 % 224 213
Test Date: 2025-03-21 11:02:39 Functions: 100.0 % 40 40

            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            2 : void SessionServerApp::kill() const
     297              : {
     298            2 :     if (m_pid > 0)
     299              :     {
     300            1 :         m_linuxWrapper->kill(m_pid, SIGKILL);
     301              :     }
     302            2 : }
     303              : 
     304            1 : void SessionServerApp::setExpectedState(const firebolt::rialto::common::SessionServerState &state)
     305              : {
     306            1 :     m_expectedState = state;
     307              : }
     308              : 
     309            1 : firebolt::rialto::common::SessionServerState SessionServerApp::getExpectedState() const
     310              : {
     311            1 :     return m_expectedState;
     312              : }
     313              : 
     314            6 : bool SessionServerApp::initializeSockets()
     315              : {
     316           12 :     if (m_linuxWrapper->socketpair(AF_UNIX, SOCK_SEQPACKET | SOCK_CLOEXEC | SOCK_NONBLOCK, 0, m_socks.data()) < 0)
     317              :     {
     318            1 :         RIALTO_SERVER_MANAGER_LOG_SYS_ERROR(errno, "socketpair failed");
     319            1 :         return false;
     320              :     }
     321            5 :     return true;
     322              : }
     323              : 
     324            5 : void SessionServerApp::setupStartupTimer()
     325              : {
     326            5 :     if (std::chrono::milliseconds(0) < m_kSessionServerStartupTimeout)
     327              :     {
     328            4 :         std::unique_lock<std::mutex> lock{m_timerMutex};
     329              :         m_startupTimer =
     330            4 :             m_timerFactory
     331           12 :                 ->createTimer(m_kSessionServerStartupTimeout,
     332            4 :                               [this]()
     333              :                               {
     334            1 :                                   RIALTO_SERVER_MANAGER_LOG_WARN("Killing: %d", m_kServerId);
     335            1 :                                   m_sessionServerAppManager
     336            1 :                                       .onSessionServerStateChanged(m_kServerId,
     337            1 :                                                                    firebolt::rialto::common::SessionServerState::ERROR);
     338            1 :                                   kill();
     339            1 :                                   m_sessionServerAppManager
     340            1 :                                       .onSessionServerStateChanged(m_kServerId,
     341            1 :                                                                    firebolt::rialto::common::SessionServerState::NOT_RUNNING);
     342            5 :                               });
     343            4 :     }
     344              :     else
     345              :     {
     346            1 :         RIALTO_SERVER_MANAGER_LOG_INFO("Startup timer disabled");
     347              :     }
     348            5 : }
     349              : 
     350            5 : bool SessionServerApp::spawnSessionServer()
     351              : {
     352           15 :     return m_linuxWrapper->vfork(
     353           10 :         [this](pid_t childPid)
     354              :         {
     355            5 :             if (childPid == -1)
     356              :             {
     357            1 :                 RIALTO_SERVER_MANAGER_LOG_SYS_ERROR(errno, "Unable to spawn RialtoSessionServer - fork problem");
     358            1 :                 m_linuxWrapper->close(m_socks[1]);
     359            1 :                 m_socks[1] = -1;
     360            1 :                 return false;
     361              :             }
     362            4 :             else if (childPid > 0)
     363              :             {
     364            1 :                 RIALTO_SERVER_MANAGER_LOG_DEBUG("%d launched. PID: %d", m_kServerId, childPid);
     365            1 :                 m_pid = childPid;
     366            1 :                 return true;
     367              :             }
     368              :             else
     369              :             {
     370            3 :                 int newSocket{-1};
     371              :                 {
     372            3 :                     std::unique_lock<std::mutex> lock{m_processStartupMutex};
     373            3 :                     newSocket = m_linuxWrapper->dup(m_socks[0]);
     374            3 :                     if (0 != m_linuxWrapper->close(m_socks[0]))
     375              :                     {
     376            3 :                         RIALTO_SERVER_MANAGER_LOG_SYS_WARN(errno, "Socket %d could not be closed in child process.",
     377              :                                                            m_socks[0]);
     378              :                     }
     379            3 :                     RIALTO_SERVER_MANAGER_LOG_DEBUG("Child socket initialized: %d", newSocket);
     380            3 :                     m_childInitialized = true;
     381            3 :                     m_processStartupCv.notify_one();
     382              :                 }
     383            3 :                 if (!firebolt::rialto::logging::isConsoleLoggingEnabled())
     384              :                 {
     385            0 :                     int devNull = m_linuxWrapper->open("/dev/null", O_RDWR, 0);
     386            0 :                     if (devNull < 0)
     387              :                     {
     388            0 :                         m_linuxWrapper->exit(EXIT_FAILURE);
     389            0 :                         return false; // wrapper function is not [[noreturn]]
     390              :                     }
     391            0 :                     m_linuxWrapper->dup2(devNull, STDIN_FILENO);
     392            0 :                     m_linuxWrapper->dup2(devNull, STDOUT_FILENO);
     393            0 :                     m_linuxWrapper->dup2(devNull, STDERR_FILENO);
     394            0 :                     if (devNull > STDERR_FILENO)
     395              :                     {
     396            0 :                         m_linuxWrapper->close(devNull);
     397            0 :                         devNull = -1;
     398              :                     }
     399              :                 }
     400            3 :                 const std::string kAppMgmtSocketStr{std::to_string(newSocket)};
     401            3 :                 char *const appArguments[] = {strdup(m_kSessionServerPath.c_str()), strdup(kAppMgmtSocketStr.c_str()),
     402            3 :                                               nullptr};
     403            3 :                 RIALTO_SERVER_MANAGER_LOG_DEBUG("PID: %d, executing: \"%s\" \"%s\"", m_linuxWrapper->getpid(),
     404              :                                                 appArguments[0], appArguments[1]);
     405            3 :                 m_linuxWrapper->execve(m_kSessionServerPath.c_str(), appArguments, m_environmentVariables.data());
     406            3 :                 RIALTO_SERVER_MANAGER_LOG_SYS_ERROR(errno, "Unable to spawn RialtoSessionServer - execve problem");
     407           12 :                 for (char *arg : appArguments)
     408              :                 {
     409            9 :                     free(arg);
     410              :                 }
     411            3 :                 m_linuxWrapper->exit(EXIT_FAILURE);
     412            3 :                 return true; // wrapper function is not [[noreturn]]
     413              :             }
     414           10 :         });
     415              : }
     416              : 
     417           15 : void SessionServerApp::waitForChildProcess()
     418              : {
     419           15 :     if (m_pid == -1)
     420              :     {
     421           14 :         return;
     422              :     }
     423              :     auto killTimer =
     424            2 :         m_timerFactory->createTimer(std::chrono::milliseconds{1000},
     425            0 :                                     [this]()
     426              :                                     {
     427            1 :                                         RIALTO_SERVER_MANAGER_LOG_ERROR("Waitpid timeout. Killing: %d", m_kServerId);
     428            1 :                                         kill();
     429            2 :                                     });
     430            1 :     if (m_linuxWrapper->waitpid(m_pid, nullptr, 0) < 0)
     431              :     {
     432            1 :         RIALTO_SERVER_MANAGER_LOG_SYS_WARN(errno, "waitpid failed for %d", m_kServerId);
     433              :     }
     434            1 :     killTimer->cancel();
     435            1 :     RIALTO_SERVER_MANAGER_LOG_DEBUG("Server with id: %d exited.", m_kServerId);
     436              : }
     437              : 
     438           10 : bool SessionServerApp::isNamedSocketInitialized() const
     439              : {
     440           10 :     return m_namedSocket != nullptr;
     441              : }
     442              : 
     443            2 : int SessionServerApp::getSessionManagementSocketFd() const
     444              : {
     445            2 :     if (m_namedSocket)
     446              :     {
     447            1 :         return m_namedSocket->getFd();
     448              :     }
     449            1 :     return -1;
     450              : }
     451              : 
     452            1 : std::unique_ptr<firebolt::rialto::ipc::INamedSocket> &&SessionServerApp::releaseNamedSocket()
     453              : {
     454            1 :     if (m_namedSocket)
     455              :     {
     456            1 :         m_namedSocket->blockNewConnections();
     457              :     }
     458            1 :     return std::move(m_namedSocket);
     459              : }
     460              : } // namespace rialto::servermanager::common
        

Generated by: LCOV version 2.0-1