Instrument Neutral Distributed Interface INDI  2.0.2
utils.cpp
Go to the documentation of this file.
1 /*******************************************************************************
2  Copyright(c) 2022 Ludovic Pollet. All rights reserved.
3 
4  This library is free software; you can redistribute it and/or
5  modify it under the terms of the GNU Library General Public
6  License version 2 as published by the Free Software Foundation.
7 
8  This library is distributed in the hope that it will be useful,
9  but WITHOUT ANY WARRANTY; without even the implied warranty of
10  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
11  Library General Public License for more details.
12 
13  You should have received a copy of the GNU Library General Public License
14  along with this library; see the file COPYING.LIB. If not, write to
15  the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
16  Boston, MA 02110-1301, USA.
17 *******************************************************************************/
18 
19 #ifndef _GNU_SOURCE
20 #define _GNU_SOURCE // needed for siginfo_t and sigaction
21 #endif
22 
23 #include <unistd.h>
24 #include <sys/types.h>
25 #include <sys/socket.h>
26 #include <sys/un.h>
27 #include <netdb.h>
28 #include <netinet/in.h>
29 #include <errno.h>
30 #include <fcntl.h>
31 #include <sys/socket.h>
32 #include <signal.h>
33 #include <system_error>
34 
35 #include "utils.h"
36 
38 {
39  signal(SIGPIPE, SIG_IGN);
40 }
41 
42 static void initUnixSocketAddr(const std::string &unixAddr, struct sockaddr_un &serv_addr_un, socklen_t &addrlen, bool bind)
43 {
44  memset(&serv_addr_un, 0, sizeof(serv_addr_un));
45  serv_addr_un.sun_family = AF_UNIX;
46 
47 #ifdef __linux__
48  (void) bind;
49 
50  // Using abstract socket path to avoid filesystem boilerplate
51  strncpy(serv_addr_un.sun_path + 1, unixAddr.c_str(), sizeof(serv_addr_un.sun_path) - 1);
52 
53  int len = offsetof(struct sockaddr_un, sun_path) + unixAddr.size() + 1;
54 
55  addrlen = len;
56 #else
57  // Using filesystem socket path
58  strncpy(serv_addr_un.sun_path, unixAddr.c_str(), sizeof(serv_addr_un.sun_path) - 1);
59 
60  int len = offsetof(struct sockaddr_un, sun_path) + unixAddr.size();
61 
62  if (bind)
63  {
64  unlink(unixAddr.c_str());
65  }
66 #endif
67  addrlen = len;
68 }
69 
70 
71 int unixSocketListen(const std::string &unixAddr)
72 {
73  struct sockaddr_un serv_addr_un;
74  socklen_t addrlen;
75 
76  int sockfd;
77  if ((sockfd = socket(AF_UNIX, SOCK_STREAM, 0)) < 0)
78  {
79  throw std::system_error(errno, std::generic_category(), "Socket");
80  }
81 
82 #ifndef __linux__
83  fcntl(sockfd, F_SETFD, FD_CLOEXEC);
84 #endif
85 
86  int reuse = 1;
87  if (setsockopt(sockfd, SOL_SOCKET, SO_REUSEADDR, &reuse, sizeof(reuse)) < 0)
88  {
89  throw std::system_error(errno, std::generic_category(), "setsockopt");
90  }
91 
92  initUnixSocketAddr(unixAddr, serv_addr_un, addrlen, true);
93 
94  if (bind(sockfd, (struct sockaddr *)&serv_addr_un, addrlen) < 0)
95  {
96  throw std::system_error(errno, std::generic_category(), "Bind to " + unixAddr);
97  }
98 
99  /* willing to accept connections with a backlog of 5 pending */
100  if (::listen(sockfd, 5) < 0)
101  {
102  throw std::system_error(errno, std::generic_category(), "Listen to " + unixAddr);
103  }
104 
105  return sockfd;
106 }
107 
108 int tcpSocketListen(int port)
109 {
110  struct sockaddr_in serv_socket;
111  int sockfd;
112  int reuse = 1;
113 
114  /* make socket endpoint */
115  if ((sockfd = socket(AF_INET, SOCK_STREAM, 0)) < 0)
116  {
117  throw std::system_error(errno, std::generic_category(), "Socket");
118  }
119 
120  /* bind to given port for any IP address */
121  memset(&serv_socket, 0, sizeof(serv_socket));
122  serv_socket.sin_family = AF_INET;
123  serv_socket.sin_addr.s_addr = htonl(INADDR_ANY);
124  serv_socket.sin_port = htons((unsigned short)port);
125  if (setsockopt(sockfd, SOL_SOCKET, SO_REUSEADDR, &reuse, sizeof(reuse)) < 0)
126  {
127  throw std::system_error(errno, std::generic_category(), "SO_REUSEADDR");
128  }
129  if (bind(sockfd, (struct sockaddr *)&serv_socket, sizeof(serv_socket)) < 0)
130  {
131  throw std::system_error(errno, std::generic_category(), "Bind to " + std::to_string(port));
132  }
133 
134  /* willing to accept connections with a backlog of 5 pending */
135  if (::listen(sockfd, 5) < 0)
136  {
137  throw std::system_error(errno, std::generic_category(), "Listen to " + std::to_string(port));
138  }
139 
140  return sockfd;
141 }
142 
143 
144 int socketAccept(int fd)
145 {
146  /* get a private connection to new client */
147  int cli_fd = ::accept(fd, 0, 0);
148  if (cli_fd < 0)
149  {
150  throw std::system_error(errno, std::generic_category(), "Accept failed");
151  }
152  return cli_fd;
153 }
154 
155 int unixSocketConnect(const std::string &unixAddr, bool failAllowed)
156 {
157  struct sockaddr_un serv_addr_un;
158  socklen_t addrlen;
159 
160  int sockfd;
161  if ((sockfd = socket(AF_UNIX, SOCK_STREAM, 0)) < 0)
162  {
163  throw std::system_error(errno, std::generic_category(), "Socket");
164  }
165 
166  initUnixSocketAddr(unixAddr, serv_addr_un, addrlen, false);
167 
168  int ret = ::connect(sockfd, (struct sockaddr *)&serv_addr_un, addrlen);
169  if (ret != -1)
170  {
171  return sockfd;
172  }
173 
174  int e = errno;
175  close(sockfd);
176  if (!failAllowed)
177  {
178  throw std::system_error(e, std::generic_category(), "Connect to " + unixAddr);
179  }
180  return -1;
181 }
182 
183 void unixSocketSendFds(int fd, int count, int * fds)
184 {
185  struct msghdr msgh;
186  struct iovec iov[1];
187  char buff[1] = {0};
188  int cmsghdrlength;
189  struct cmsghdr * cmsgh;
190 
191 
192  cmsghdrlength = CMSG_SPACE((count * sizeof(int)));
193  // FIXME: abort on alloc error here
194  cmsgh = (struct cmsghdr*)malloc(cmsghdrlength);
195 
196  /* Write the fd as ancillary data */
197  cmsgh->cmsg_len = CMSG_LEN(count * sizeof(int));
198  cmsgh->cmsg_level = SOL_SOCKET;
199  cmsgh->cmsg_type = SCM_RIGHTS;
200  msgh.msg_control = cmsgh;
201  msgh.msg_controllen = cmsghdrlength;
202  for(int i = 0; i < count; ++i)
203  {
204  ((int *) CMSG_DATA(CMSG_FIRSTHDR(&msgh)))[i] = fds[i];
205  }
206 
207  iov[0].iov_base = buff;
208  iov[0].iov_len = 1;
209 
210  msgh.msg_flags = 0;
211  msgh.msg_name = NULL;
212  msgh.msg_namelen = 0;
213  msgh.msg_iov = iov;
214  msgh.msg_iovlen = 1;
215 
216  int ret = sendmsg(fd, &msgh, MSG_NOSIGNAL);
217 
218  if (ret == -1)
219  {
220  throw std::system_error(errno, std::generic_category(), "Failed to send fds");
221  }
222  if (ret == 0)
223  {
224  throw std::runtime_error("Channel closed when sending fds");
225  }
226 }
227 
228 void unixSocketRecvFds(int fd, int count, int * fdsDest)
229 {
230  char buf[1];
231  struct msghdr msgh;
232  struct iovec iov;
233 
234  union
235  {
236  struct cmsghdr cmsgh;
237  /* Space large enough to hold an 'int' */
238  char control[CMSG_SPACE(16 * sizeof(int))];
239  } control_un;
240 
241  if (count > 16)
242  {
243  throw std::runtime_error("Cannot pass that amount of fds");
244  }
245 
246 
247 
248  iov.iov_base = buf;
249  iov.iov_len = 1;
250 
251  msgh.msg_name = NULL;
252  msgh.msg_namelen = 0;
253  msgh.msg_iov = &iov;
254  msgh.msg_iovlen = 1;
255  msgh.msg_flags = 0;
256  msgh.msg_control = control_un.control;
257  msgh.msg_controllen = sizeof(control_un.control);
258 
259  int recvflag;
260 #ifdef __linux__
261  recvflag = MSG_CMSG_CLOEXEC;
262 #else
263  recvflag = 0;
264 #endif
265 
266  int size = recvmsg(fd, &msgh, recvflag);
267  if (size == -1)
268  {
269  throw std::system_error(errno, std::generic_category(), "Could not receive fds");
270  }
271 
272  for (struct cmsghdr * cmsg = CMSG_FIRSTHDR(&msgh); cmsg != NULL; cmsg = CMSG_NXTHDR(&msgh, cmsg))
273  {
274  if (cmsg->cmsg_level == SOL_SOCKET && cmsg->cmsg_type == SCM_RIGHTS)
275  {
276  int fdCount = 0;
277  while(cmsg->cmsg_len >= CMSG_LEN((fdCount + 1) * sizeof(int)))
278  {
279  fdCount++;
280  }
281  fprintf(stderr, "Received fd : %d\n", fdCount);
282  if (fdCount != count)
283  {
284  throw std::runtime_error("Wrong number of fds received");
285  }
286  int * fds = (int*)CMSG_DATA(cmsg);
287  for(int i = 0; i < fdCount; ++i)
288  {
289 #ifndef __linux__
290  fcntl(fds[i], F_SETFD, FD_CLOEXEC);
291 #endif
292  fdsDest[i] = fds[i];
293  }
294 
295  return;
296  }
297  }
298  throw std::runtime_error("Did not receive fds");
299 }
300 
301 int tcpSocketConnect(const std::string &host, int port, bool failAllowed)
302 {
303  struct sockaddr_in serv_addr;
304  int sockfd;
305 
306  /* lookup host address */
307  auto hp = gethostbyname(host.c_str());
308  if (!hp)
309  {
310  throw std::system_error(h_errno, std::generic_category(), "Could not resolve " + host);
311  }
312 
313  /* create a socket to the INDI server */
314  (void)memset((char *)&serv_addr, 0, sizeof(serv_addr));
315  serv_addr.sin_family = AF_INET;
316  serv_addr.sin_addr.s_addr = ((struct in_addr *)(hp->h_addr_list[0]))->s_addr;
317  serv_addr.sin_port = htons(port);
318  if ((sockfd = socket(AF_INET, SOCK_STREAM, 0)) < 0)
319  {
320  throw std::system_error(errno, std::generic_category(), "socket");
321  }
322 
323  /* connect */
324  if (connect(sockfd, (struct sockaddr *)&serv_addr, sizeof(serv_addr)) < 0)
325  {
326  if (!failAllowed)
327  {
328  throw std::system_error(errno, std::generic_category(), "Connect to " + host);
329  }
330  auto e = errno;
331  close(sockfd);
332  errno = e;
333  return -1;
334  }
335 
336  return sockfd;
337 }
338 
339 
340 std::string getTestExePath(const std::string &str)
341 {
342  size_t size = 256;
343  char * buffer;
344 
345  buffer = (char*)malloc(size);
346 
347  while(true)
348  {
349  if (getcwd(buffer, size) != nullptr)
350  {
351  break;
352  }
353  if ((errno == ERANGE) && (size < 0x100000))
354  {
355  size *= 2;
356  buffer = (char*)realloc(buffer, size);
357  }
358  else
359  {
360  perror("getcwd");
361  exit(255);
362  }
363  }
364  std::string ret = std::string(buffer) + "/" + str;
365  fprintf(stderr, "starting : %s\n", ret.c_str());
366  free(buffer);
367  return ret;
368 }
int errno
int fd
Definition: intelliscope.c:43
std::vector< uint8_t > buffer
NLOHMANN_BASIC_JSON_TPL_DECLARATION std::string to_string(const NLOHMANN_BASIC_JSON_TPL &j)
user-defined to_string function for JSON values
Definition: json.h:23613
int socketAccept(int fd)
Definition: utils.cpp:144
std::string getTestExePath(const std::string &str)
Definition: utils.cpp:340
int unixSocketListen(const std::string &unixAddr)
Definition: utils.cpp:71
int tcpSocketListen(int port)
Definition: utils.cpp:108
void unixSocketRecvFds(int fd, int count, int *fdsDest)
Definition: utils.cpp:228
int unixSocketConnect(const std::string &unixAddr, bool failAllowed)
Definition: utils.cpp:155
void unixSocketSendFds(int fd, int count, int *fds)
Definition: utils.cpp:183
void setupSigPipe()
Definition: utils.cpp:37
int tcpSocketConnect(const std::string &host, int port, bool failAllowed)
Definition: utils.cpp:301