blob: 3eb5d30ae4d2008db5094478ad42ac07134e5acd [file] [log] [blame]
Artem Senicheve8837d52020-06-07 11:59:04 +03001// SPDX-License-Identifier: Apache-2.0
2// Copyright (C) 2020 YADRO
3
4#include "host_console.hpp"
5
6#include <sys/ioctl.h>
7#include <sys/socket.h>
8#include <sys/un.h>
9#include <unistd.h>
10
11#include <cstring>
12#include <system_error>
13
14/**
15 * @brief Base path to the console's socket.
16 * See obmc-console for details.
17 */
Chau Ly47734e22023-07-19 08:03:18 +000018static constexpr char socketPath[] = "\0obmc-console";
19static constexpr std::string defaultSocketId = "default";
Artem Senicheve8837d52020-06-07 11:59:04 +030020
21HostConsole::HostConsole(const std::string& socketId) :
22 socketId(socketId), socketFd(-1)
23{}
24
25HostConsole::~HostConsole()
26{
27 if (socketFd != -1)
28 {
29 close(socketFd);
30 }
31}
32
33void HostConsole::connect()
34{
35 if (socketFd != -1)
36 {
37 throw std::runtime_error("Socket already opened");
38 }
39
40 // Create socket
41 socketFd = socket(AF_UNIX, SOCK_STREAM, 0);
42 if (socketFd == -1)
43 {
44 std::error_code ec(errno ? errno : EIO, std::generic_category());
45 throw std::system_error(ec, "Unable to create socket");
46 }
47
48 // Set non-blocking mode for socket
49 int opt = 1;
50 if (ioctl(socketFd, FIONBIO, &opt))
51 {
52 std::error_code ec(errno ? errno : EIO, std::generic_category());
53 throw std::system_error(ec, "Unable to set non-blocking mode");
54 }
55
56 // Construct path to the socket file (see obmc-console for details)
57 std::string path(socketPath, socketPath + sizeof(socketPath) - 1);
Chau Ly47734e22023-07-19 08:03:18 +000058 path += '.';
Artem Senicheve8837d52020-06-07 11:59:04 +030059 if (!socketId.empty())
60 {
Artem Senicheve8837d52020-06-07 11:59:04 +030061 path += socketId;
62 }
Chau Ly47734e22023-07-19 08:03:18 +000063 else
64 {
65 path += defaultSocketId;
66 }
Artem Senicheve8837d52020-06-07 11:59:04 +030067 if (path.length() > sizeof(sockaddr_un::sun_path))
68 {
69 throw std::invalid_argument("Invalid socket ID");
70 }
71
72 sockaddr_un sa;
73 sa.sun_family = AF_UNIX;
74 memcpy(&sa.sun_path, path.c_str(), path.length());
75
76 // Connect to host's log stream via socket.
77 // The owner of the socket (server) is obmc-console service and
78 // we have a dependency on it written in the systemd unit file, but
79 // we can't guarantee that the socket is initialized at the moment.
80 size_t connectAttempts = 60; // Number of attempts
81 const socklen_t len = sizeof(sa) - sizeof(sa.sun_path) + path.length();
82 int rc = -1;
83 while (connectAttempts--)
84 {
85 rc = ::connect(socketFd, reinterpret_cast<const sockaddr*>(&sa), len);
86 if (!rc)
87 {
88 break;
89 }
90 else
91 {
92 sleep(1); // Make 1 second pause between attempts
93 }
94 }
95 if (rc)
96 {
97 std::string err = "Unable to connect to console";
98 if (!socketId.empty())
99 {
100 err += ' ';
101 err += socketId;
102 }
103 std::error_code ec(errno ? errno : EIO, std::generic_category());
104 throw std::system_error(ec, err);
105 }
106}
107
108size_t HostConsole::read(char* buf, size_t sz) const
109{
110 ssize_t rsz = ::read(socketFd, buf, sz);
111 if (rsz < 0)
112 {
113 if (errno == EAGAIN || errno == EWOULDBLOCK)
114 {
115 // We are in non-blocking mode, so ignore these codes
116 rsz = 0;
117 }
118 else
119 {
120 std::string err = "Unable to read socket";
121 if (!socketId.empty())
122 {
123 err += ' ';
124 err += socketId;
125 }
126 std::error_code ec(errno ? errno : EIO, std::generic_category());
127 throw std::system_error(ec, err);
128 }
129 }
130
131 return static_cast<size_t>(rsz);
132}
133
134HostConsole::operator int() const
135{
136 return socketFd;
137}