LCOV - code coverage report
Current view: top level - serverManager/common/source - SessionServerApp.cpp (source / functions) Coverage Total Hit
Test: coverage.info Lines: 95.0 % 219 208
Test Date: 2026-09-11 17:36:12 Functions: 100.0 % 42 42

            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
        

Generated by: LCOV version 2.0-1