LCOV - code coverage report
Current view: top level - ipc/common/source - NamedSocket.cpp (source / functions) Coverage Total Hit
Test: coverage.info Lines: 72.8 % 151 110
Test Date: 2026-07-07 06:27:16 Functions: 100.0 % 15 15

            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 2025 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 "NamedSocket.h"
      21              : #include "IpcLogging.h"
      22              : #include <cstdint>
      23              : #include <grp.h>
      24              : #include <pwd.h>
      25              : #include <stdexcept>
      26              : #include <sys/file.h>
      27              : #include <sys/socket.h>
      28              : #include <sys/stat.h>
      29              : #include <sys/un.h>
      30              : #include <unistd.h>
      31              : #include <utility>
      32              : 
      33              : namespace
      34              : {
      35              : constexpr uid_t kNoOwnerChange = -1; // -1 means chown() won't change the owner
      36              : constexpr gid_t kNoGroupChange = -1; // -1 means chown() won't change the group
      37              : } // namespace
      38              : 
      39              : namespace firebolt::rialto::ipc
      40              : {
      41            6 : INamedSocketFactory &INamedSocketFactory::getFactory()
      42              : {
      43            6 :     static NamedSocketFactory factory;
      44            6 :     return factory;
      45              : }
      46              : 
      47            3 : std::unique_ptr<INamedSocket> NamedSocketFactory::createNamedSocket() const
      48              : try
      49              : {
      50            3 :     return std::make_unique<NamedSocket>();
      51              : }
      52            0 : catch (const std::runtime_error &error)
      53              : {
      54            0 :     RIALTO_IPC_LOG_ERROR("Failed to create named socket: %s", error.what());
      55            0 :     return nullptr;
      56              : }
      57              : 
      58            3 : std::unique_ptr<INamedSocket> NamedSocketFactory::createNamedSocket(const std::string &socketPath) const
      59              : try
      60              : {
      61            3 :     return std::make_unique<NamedSocket>(socketPath);
      62              : }
      63            0 : catch (const std::runtime_error &error)
      64              : {
      65            0 :     RIALTO_IPC_LOG_ERROR("Failed to create named socket: %s", error.what());
      66            0 :     return nullptr;
      67              : }
      68              : 
      69            3 : NamedSocket::NamedSocket()
      70              : {
      71            3 :     RIALTO_IPC_LOG_MIL("Creating new socket without binding");
      72              : 
      73              :     // Create the socket
      74            3 :     m_sockFd = ::socket(AF_UNIX, SOCK_SEQPACKET | SOCK_NONBLOCK, 0);
      75            3 :     if (m_sockFd == -1)
      76              :     {
      77            0 :         RIALTO_IPC_LOG_SYS_ERROR(errno, "socket error");
      78            0 :         throw std::runtime_error("socket error");
      79              :     }
      80              : 
      81            3 :     RIALTO_IPC_LOG_MIL("Socket created, fd: %d", m_sockFd);
      82              : }
      83              : 
      84            3 : NamedSocket::NamedSocket(const std::string &socketPath)
      85              : {
      86            3 :     RIALTO_IPC_LOG_MIL("Creating named socket with name: %s", socketPath.c_str());
      87            3 :     m_sockPath = socketPath;
      88              : 
      89              :     // Create the socket
      90            3 :     m_sockFd = ::socket(AF_UNIX, SOCK_SEQPACKET | SOCK_NONBLOCK, 0);
      91            3 :     if (m_sockFd == -1)
      92              :     {
      93            0 :         RIALTO_IPC_LOG_SYS_ERROR(errno, "socket error");
      94            0 :         throw std::runtime_error("socket error");
      95              :     }
      96              : 
      97              :     // get the socket lock
      98            3 :     if (!getSocketLock())
      99              :     {
     100            0 :         closeListeningSocket();
     101            0 :         throw std::runtime_error("lock error");
     102              :     }
     103              : 
     104              :     // bind to the given path
     105            3 :     struct sockaddr_un addr = {0};
     106            3 :     memset(&addr, 0x00, sizeof(addr));
     107            3 :     addr.sun_family = AF_UNIX;
     108            3 :     strncpy(addr.sun_path, socketPath.c_str(), sizeof(addr.sun_path) - 1);
     109              : 
     110            3 :     if (::bind(m_sockFd, reinterpret_cast<struct sockaddr *>(&addr), sizeof(addr)) == -1)
     111              :     {
     112            0 :         RIALTO_IPC_LOG_SYS_ERROR(errno, "bind error");
     113              : 
     114            0 :         closeListeningSocket();
     115            0 :         throw std::runtime_error("bind error");
     116              :     }
     117              : 
     118            3 :     RIALTO_IPC_LOG_MIL("Named socket with name: %s created, fd: %d", m_sockPath.c_str(), m_sockFd);
     119              : }
     120              : 
     121           12 : NamedSocket::~NamedSocket()
     122              : {
     123            6 :     RIALTO_IPC_LOG_MIL("Close named socket with name: %s, fd: %d", m_sockPath.c_str(), m_sockFd);
     124            6 :     closeListeningSocket();
     125           12 : }
     126              : 
     127            4 : int NamedSocket::getFd() const
     128              : {
     129            4 :     return m_sockFd;
     130              : }
     131              : 
     132            1 : bool NamedSocket::setSocketPermissions(unsigned int socketPermissions) const
     133              : {
     134            1 :     errno = 0;
     135            1 :     if (chmod(m_sockPath.c_str(), socketPermissions) != 0)
     136              :     {
     137            0 :         RIALTO_IPC_LOG_SYS_WARN(errno, "Failed to change the permissions on the IPC socket");
     138            0 :         return false;
     139              :     }
     140            1 :     return true;
     141              : }
     142              : 
     143            1 : bool NamedSocket::setSocketOwnership(const std::string &socketOwner, const std::string &socketGroup) const
     144              : {
     145            1 :     uid_t ownerId = getSocketOwnerId(socketOwner);
     146            1 :     gid_t groupId = getSocketGroupId(socketGroup);
     147              : 
     148            1 :     if (ownerId != kNoOwnerChange || groupId != kNoGroupChange)
     149              :     {
     150            1 :         errno = 0;
     151            1 :         if (chown(m_sockPath.c_str(), ownerId, groupId) != 0)
     152              :         {
     153            0 :             RIALTO_IPC_LOG_SYS_WARN(errno, "Failed to change the owner/group for the IPC socket");
     154              :         }
     155              :     }
     156            1 :     return true;
     157              : }
     158              : 
     159            2 : bool NamedSocket::blockNewConnections() const
     160              : {
     161            2 :     if (m_sockPath.empty())
     162              :     {
     163            1 :         RIALTO_IPC_LOG_DEBUG("No need to block new connections - socket not configured");
     164            1 :         return true;
     165              :     }
     166            1 :     RIALTO_IPC_LOG_INFO("Block new connections for: %s", m_sockPath.c_str());
     167            1 :     if (listen(m_sockFd, 0) == -1)
     168              :     {
     169            0 :         RIALTO_IPC_LOG_SYS_ERROR(errno, "blockNewConnections: listen error");
     170            0 :         return false;
     171              :     }
     172            1 :     return true;
     173              : }
     174              : 
     175            3 : bool NamedSocket::bind(const std::string &socketPath)
     176              : {
     177            3 :     if (!m_sockPath.empty())
     178              :     {
     179            1 :         RIALTO_IPC_LOG_DEBUG("no need to bind again");
     180            1 :         return true;
     181              :     }
     182            2 :     RIALTO_IPC_LOG_MIL("Binding socket with fd: %d with name: %s", m_sockFd, socketPath.c_str());
     183            2 :     m_sockPath = socketPath;
     184              : 
     185              :     // get the socket lock
     186            2 :     if (!getSocketLock())
     187              :     {
     188            0 :         closeListeningSocket();
     189            0 :         return false;
     190              :     }
     191              : 
     192              :     // bind to the given path
     193            2 :     struct sockaddr_un addr = {0};
     194            2 :     memset(&addr, 0x00, sizeof(addr));
     195            2 :     addr.sun_family = AF_UNIX;
     196            2 :     strncpy(addr.sun_path, socketPath.c_str(), sizeof(addr.sun_path) - 1);
     197              : 
     198            2 :     if (::bind(m_sockFd, reinterpret_cast<struct sockaddr *>(&addr), sizeof(addr)) == -1)
     199              :     {
     200            0 :         RIALTO_IPC_LOG_SYS_ERROR(errno, "bind error");
     201              : 
     202            0 :         closeListeningSocket();
     203            0 :         return false;
     204              :     }
     205              : 
     206            2 :     RIALTO_IPC_LOG_MIL("Named socket with fd: %d bound with path: %s", m_sockFd, m_sockPath.c_str());
     207              : 
     208            2 :     return true;
     209              : }
     210              : 
     211            6 : void NamedSocket::closeListeningSocket()
     212              : {
     213            6 :     if (!m_sockPath.empty() && (unlink(m_sockPath.c_str()) != 0) && (errno != ENOENT))
     214            0 :         RIALTO_IPC_LOG_SYS_ERROR(errno, "failed to remove socket @ '%s'", m_sockPath.c_str());
     215            6 :     if ((m_sockFd >= 0) && (close(m_sockFd) != 0))
     216            0 :         RIALTO_IPC_LOG_SYS_ERROR(errno, "failed to close listening socket");
     217              : 
     218            6 :     if (!m_lockPath.empty() && (unlink(m_lockPath.c_str()) != 0) && (errno != ENOENT))
     219            0 :         RIALTO_IPC_LOG_SYS_ERROR(errno, "failed to remove socket lock file @ '%s'", m_lockPath.c_str());
     220            6 :     if ((m_lockFd >= 0) && (close(m_lockFd) != 0))
     221            0 :         RIALTO_IPC_LOG_SYS_ERROR(errno, "failed to close socket lock file");
     222              : 
     223            6 :     m_sockFd = -1;
     224            6 :     m_sockPath.clear();
     225              : 
     226            6 :     m_lockFd = -1;
     227            6 :     m_lockPath.clear();
     228              : }
     229              : 
     230            5 : bool NamedSocket::getSocketLock()
     231              : {
     232            5 :     std::string lockPath = m_sockPath + ".lock";
     233            5 :     int fd = open(lockPath.c_str(), O_CREAT | O_CLOEXEC, (S_IRUSR | S_IWUSR | S_IRGRP | S_IWGRP));
     234            5 :     if (fd < 0)
     235              :     {
     236            0 :         RIALTO_IPC_LOG_SYS_ERROR(errno, "failed to create / open lockfile @ '%s' (check permissions)", lockPath.c_str());
     237            0 :         return false;
     238              :     }
     239              : 
     240            5 :     if (flock(fd, LOCK_EX | LOCK_NB) < 0)
     241              :     {
     242            0 :         RIALTO_IPC_LOG_SYS_ERROR(errno, "failed to lock lockfile @ '%s', maybe another server is running",
     243              :                                  lockPath.c_str());
     244            0 :         close(fd);
     245            0 :         return false;
     246              :     }
     247              : 
     248            5 :     struct stat sbuf = {0};
     249            5 :     if (stat(m_sockPath.c_str(), &sbuf) < 0)
     250              :     {
     251            5 :         if (errno != ENOENT)
     252              :         {
     253            0 :             RIALTO_IPC_LOG_SYS_ERROR(errno, "did not manage to stat existing socket @ '%s'", m_sockPath.c_str());
     254            0 :             close(fd);
     255            0 :             return false;
     256              :         }
     257              :     }
     258            0 :     else if ((sbuf.st_mode & S_IWUSR) || (sbuf.st_mode & S_IWGRP))
     259              :     {
     260            0 :         unlink(m_sockPath.c_str());
     261              :     }
     262              : 
     263            5 :     m_lockFd = fd;
     264            5 :     m_lockPath = std::move(lockPath);
     265              : 
     266            5 :     return true;
     267              : }
     268              : 
     269            1 : uid_t NamedSocket::getSocketOwnerId(const std::string &socketOwner) const
     270              : {
     271            1 :     uid_t ownerId = kNoOwnerChange;
     272              :     // sysconf returns long; -1 on error. Store as int64_t to avoid unsigned conversion issues.
     273            1 :     const int64_t bufferSizeLong = sysconf(_SC_GETPW_R_SIZE_MAX);
     274            1 :     if (!socketOwner.empty() && bufferSizeLong > 0)
     275              :     {
     276            1 :         const size_t kBufferSize = static_cast<size_t>(bufferSizeLong);
     277            1 :         errno = 0;
     278            1 :         passwd passwordStruct{};
     279            1 :         passwd *passwordResult = nullptr;
     280            1 :         char buffer[kBufferSize];
     281            1 :         int result = getpwnam_r(socketOwner.c_str(), &passwordStruct, buffer, kBufferSize, &passwordResult);
     282            1 :         if (result == 0 && passwordResult)
     283              :         {
     284            1 :             ownerId = passwordResult->pw_uid;
     285              :         }
     286              :         else
     287              :         {
     288            0 :             RIALTO_IPC_LOG_SYS_WARN(errno, "Failed to determine ownerId for '%s'", socketOwner.c_str());
     289              :         }
     290            1 :     }
     291            1 :     return ownerId;
     292              : }
     293              : 
     294            1 : gid_t NamedSocket::getSocketGroupId(const std::string &socketGroup) const
     295              : {
     296            1 :     gid_t groupId = kNoGroupChange;
     297              :     // sysconf returns long; -1 on error. Store as int64_t to avoid unsigned conversion issues.
     298            1 :     const int64_t bufferSizeLong = sysconf(_SC_GETGR_R_SIZE_MAX);
     299            1 :     if (!socketGroup.empty() && bufferSizeLong > 0)
     300              :     {
     301            1 :         const size_t kBufferSize = static_cast<size_t>(bufferSizeLong);
     302            1 :         errno = 0;
     303            1 :         group groupStruct{};
     304            1 :         group *groupResult = nullptr;
     305            1 :         char buffer[kBufferSize];
     306            1 :         int result = getgrnam_r(socketGroup.c_str(), &groupStruct, buffer, kBufferSize, &groupResult);
     307            1 :         if (result == 0 && groupResult)
     308              :         {
     309            1 :             groupId = groupResult->gr_gid;
     310              :         }
     311              :         else
     312              :         {
     313            0 :             RIALTO_IPC_LOG_SYS_WARN(errno, "Failed to determine groupId for '%s'", socketGroup.c_str());
     314              :         }
     315            1 :     }
     316            1 :     return groupId;
     317              : }
     318              : } // namespace firebolt::rialto::ipc
        

Generated by: LCOV version 2.0-1