io.cpp
Go to the documentation of this file.
1 /*
2  * Software License Agreement (BSD License)
3  *
4  * Copyright (c) 2008, Willow Garage, Inc.
5  * All rights reserved.
6  *
7  * Redistribution and use in source and binary forms, with or without
8  * modification, are permitted provided that the following conditions
9  * are met:
10  *
11  * * Redistributions of source code must retain the above copyright
12  * notice, this list of conditions and the following disclaimer.
13  * * Redistributions in binary form must reproduce the above
14  * copyright notice, this list of conditions and the following
15  * disclaimer in the documentation and/or other materials provided
16  * with the distribution.
17  * * Neither the name of Willow Garage, Inc. nor the names of its
18  * contributors may be used to endorse or promote products derived
19  * from this software without specific prior written permission.
20  *
21  * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
22  * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
23  * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
24  * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
25  * COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
26  * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
27  * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
28  * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
29  * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
30  * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
31  * ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
32  * POSSIBILITY OF SUCH DAMAGE.
33  */
34 /*****************************************************************************
35 ** Includes
36 *****************************************************************************/
37 
38 #include "config.h"
39 
40 #include <ros/io.h>
41 #include <ros/assert.h> // don't need if we dont call the pipe functions.
42 #include <ros/file_log.h>
43 #include <errno.h> // for EFAULT and co.
44 #include <iostream>
45 #include <sstream>
46 #ifdef WIN32
47 #else
48  #include <cstring> // strerror
49  #include <fcntl.h> // for non-blocking configuration
50 #endif
51 
52 #ifdef HAVE_EPOLL
53  #include <sys/epoll.h>
54 #endif
55 
56 /*****************************************************************************
57 ** Macros
58 *****************************************************************************/
59 
60 #define UNUSED(expr) do { (void)(expr); } while (0)
61 
62 /*****************************************************************************
63 ** Namespaces
64 *****************************************************************************/
65 
66 namespace ros {
67 
68 int last_socket_error() {
69  #ifdef WIN32
70  return WSAGetLastError();
71  #else
72  return errno;
73  #endif
74 }
75 
76 const char* socket_error_string(int err) {
77 #ifdef WIN32
78  // could fix this to use FORMAT_MESSAGE and print a real string later,
79  // but not high priority.
80  std::stringstream ostream;
81  ostream << "WSA Error: " << err;
82  return ostream.str().c_str();
83 #else
84  return strerror(err);
85 #endif
86 }
87 
88 const char* last_socket_error_string() {
89  #ifdef WIN32
90  return socket_error_string(WSAGetLastError());
91  #else
92  return socket_error_string(errno);
93  #endif
94 }
95 
97 #if defined(WIN32)
98  if ( WSAGetLastError() == WSAEWOULDBLOCK ) {
99  return true;
100  } else {
101  return false;
102  }
103 #else
104  if ( ( errno == EAGAIN ) || ( errno == EWOULDBLOCK ) ) { // posix permits either
105  return true;
106  } else {
107  return false;
108  }
109 #endif
110 }
111 
112 int create_socket_watcher() {
113  int epfd = -1;
114 #if defined(HAVE_EPOLL)
115  epfd = ::epoll_create1(0);
116  if (epfd < 0)
117  {
118  ROS_ERROR("Unable to create epoll watcher: %s", strerror(errno));
119  }
120 #endif
121  return epfd;
122 }
123 
124 void close_socket_watcher(int fd) {
125  if (fd >= 0)
126  ::close(fd);
127 }
128 
129 void add_socket_to_watcher(int epfd, int fd) {
130 #if defined(HAVE_EPOLL)
131  struct epoll_event ev;
132  bzero(&ev, sizeof(ev));
133 
134  ev.events = 0;
135  ev.data.fd = fd;
136 
137  if (::epoll_ctl(epfd, EPOLL_CTL_ADD, fd, &ev))
138  {
139  ROS_ERROR("Unable to add FD to epoll: %s", strerror(errno));
140  }
141 #else
142  UNUSED(epfd);
143  UNUSED(fd);
144 #endif
145 }
146 
147 void del_socket_from_watcher(int epfd, int fd) {
148 #if defined(HAVE_EPOLL)
149  if (::epoll_ctl(epfd, EPOLL_CTL_DEL, fd, NULL))
150  {
151  ROS_ERROR("Unable to remove FD to epoll: %s", strerror(errno));
152  }
153 #else
154  UNUSED(epfd);
155  UNUSED(fd);
156 #endif
157 }
158 
159 void set_events_on_socket(int epfd, int fd, int events) {
160 #if defined(HAVE_EPOLL)
161  struct epoll_event ev;
162  bzero(&ev, sizeof(ev));
163 
164  ev.events = events;
165  ev.data.fd = fd;
166  if (::epoll_ctl(epfd, EPOLL_CTL_MOD, fd, &ev))
167  {
168  ROS_ERROR("Unable to modify FD epoll: %s", strerror(errno));
169  }
170 #else
171  UNUSED(epfd);
172  UNUSED(fd);
173  UNUSED(events);
174 #endif
175 }
176 
177 
178 
179 /*****************************************************************************
180 ** Service Robotics/Libssh Functions
181 *****************************************************************************/
194 pollfd_vector_ptr poll_sockets(int epfd, socket_pollfd *fds, nfds_t nfds, int timeout) {
195 #if defined(WIN32)
196  fd_set readfds, writefds, exceptfds;
197  struct timeval tv, *ptv;
198  socket_fd_t max_fd;
199  int rc;
200  nfds_t i;
202 
203  UNUSED(epfd);
204 
205  if (fds == NULL) {
206  errno = EFAULT;
207  return ofds;
208  }
209 
210  FD_ZERO (&readfds);
211  FD_ZERO (&writefds);
212  FD_ZERO (&exceptfds);
213 
214  /*********************
215  ** Compute fd sets
216  **********************/
217  // also find the largest descriptor.
218  for (rc = -1, max_fd = 0, i = 0; i < nfds; i++) {
219  if (fds[i].fd == INVALID_SOCKET) {
220  continue;
221  }
222  if (fds[i].events & (POLLIN | POLLRDNORM)) {
223  FD_SET (fds[i].fd, &readfds);
224  }
225  if (fds[i].events & (POLLOUT | POLLWRNORM | POLLWRBAND)) {
226  FD_SET (fds[i].fd, &writefds);
227  }
228  if (fds[i].events & (POLLPRI | POLLRDBAND)) {
229  FD_SET (fds[i].fd, &exceptfds);
230  }
231  if (fds[i].fd > max_fd &&
232  (fds[i].events & (POLLIN | POLLOUT | POLLPRI |
233  POLLRDNORM | POLLRDBAND |
234  POLLWRNORM | POLLWRBAND))) {
235  max_fd = fds[i].fd;
236  rc = 0;
237  }
238  }
239 
240  if (rc == -1) {
241  errno = EINVAL;
242  return ofds;
243  }
244  /*********************
245  ** Setting the timeout
246  **********************/
247  if (timeout < 0) {
248  ptv = NULL;
249  } else {
250  ptv = &tv;
251  if (timeout == 0) {
252  tv.tv_sec = 0;
253  tv.tv_usec = 0;
254  } else {
255  tv.tv_sec = timeout / 1000;
256  tv.tv_usec = (timeout % 1000) * 1000;
257  }
258  }
259 
260  rc = select (max_fd + 1, &readfds, &writefds, &exceptfds, ptv);
261  if (rc < 0) {
262  return ofds;
263  }
264  ofds.reset(new std::vector<socket_pollfd>);
265  if ( rc == 0 ) {
266  return ofds;
267  }
268 
269  for (rc = 0, i = 0; i < nfds; i++) {
270  if (fds[i].fd != INVALID_SOCKET) {
271  fds[i].revents = 0;
272 
273  if (FD_ISSET(fds[i].fd, &readfds)) {
274  int save_errno = errno;
275  char data[64] = {0};
276  int ret;
277 
278  /* support for POLLHUP */
279  // just check if there's incoming data, without removing it from the queue.
280  ret = recv(fds[i].fd, data, 64, MSG_PEEK);
281  #ifdef WIN32
282  if ((ret == -1) &&
283  (errno == WSAESHUTDOWN || errno == WSAECONNRESET ||
284  (errno == WSAECONNABORTED) || errno == WSAENETRESET))
285  #else
286  if ((ret == -1) &&
287  (errno == ESHUTDOWN || errno == ECONNRESET ||
288  (errno == ECONNABORTED) || errno == ENETRESET))
289  #endif
290  {
291  fds[i].revents |= POLLHUP;
292  } else {
293  fds[i].revents |= fds[i].events & (POLLIN | POLLRDNORM);
294  }
295  errno = save_errno;
296  }
297  if (FD_ISSET(fds[i].fd, &writefds)) {
298  fds[i].revents |= fds[i].events & (POLLOUT | POLLWRNORM | POLLWRBAND);
299  }
300 
301  if (FD_ISSET(fds[i].fd, &exceptfds)) {
302  fds[i].revents |= fds[i].events & (POLLPRI | POLLRDBAND);
303  }
304 
305  if (fds[i].revents & ~POLLHUP) {
306  rc++;
307  }
308  } else {
309  fds[i].revents = POLLNVAL;
310  }
311  ofds->push_back(fds[i]);
312  }
313  return ofds;
314 #elif defined (HAVE_EPOLL)
315  UNUSED(nfds);
316  UNUSED(fds);
317  struct epoll_event ev[nfds];
318  pollfd_vector_ptr ofds;
319 
320  int fd_cnt = ::epoll_wait(epfd, ev, nfds, timeout);
321 
322  if (fd_cnt < 0)
323  {
324  // EINTR means that we got interrupted by a signal, and is not an error
325  if(errno != EINTR) {
326  ROS_ERROR("Error in epoll_wait! %s", strerror(errno));
327  ofds.reset();
328  }
329  }
330  else
331  {
332  ofds.reset(new std::vector<socket_pollfd>);
333  for (int i = 0; i < fd_cnt; i++)
334  {
335  socket_pollfd pfd;
336  pfd.fd = ev[i].data.fd;
337  pfd.revents = ev[i].events;
338  ofds->push_back(pfd);
339  }
340  }
341  return ofds;
342 #else
343  UNUSED(epfd);
344  pollfd_vector_ptr ofds(new std::vector<socket_pollfd>);
345  // Clear the `revents` fields
346  for (nfds_t i = 0; i < nfds; i++)
347  {
348  fds[i].revents = 0;
349  }
350 
351  // use an existing poll implementation
352  int result = poll(fds, nfds, timeout);
353  if ( result < 0 )
354  {
355  // EINTR means that we got interrupted by a signal, and is not an error
356  if(errno != EINTR)
357  {
358  ROS_ERROR("Error in poll! %s", strerror(errno));
359  ofds.reset();
360  }
361  } else {
362  for (nfds_t i = 0; i < nfds; i++)
363  {
364  if (fds[i].revents)
365  {
366  ofds->push_back(fds[i]);
367  fds[i].revents = 0;
368  }
369  }
370  }
371  return ofds;
372 #endif // poll_sockets functions
373 }
374 /*****************************************************************************
375 ** Socket Utilities
376 *****************************************************************************/
382 int is_async_connected(socket_fd_t &socket, int &err) {
383 #ifdef HAVE_EPOLL
384  int nfds = 1;
385  int epfd = create_socket_watcher();
386  add_socket_to_watcher(epfd, socket);
387  set_events_on_socket(epfd, socket, EPOLLIN | EPOLLOUT);
388  struct epoll_event ev[nfds];
389  int fd_cnt = ::epoll_wait(epfd, ev, nfds, 0);
390  close_socket_watcher(epfd);
391  if (fd_cnt < 0) {
392  if (errno != EINTR) {
393  ROS_ERROR("Error in epoll_wait! %s", strerror(errno));
394  err = errno;
395  return -1;
396  }
397  } else if (fd_cnt == 0) {
398  err = 0;
399  return 0;
400  } else if (ev[0].events & EPOLLERR || ev[0].events & EPOLLHUP) {
401  // attempt to retrieve the error on the file descriptor epoll is waiting on
402  err = 0;
403  socklen_t errlen = sizeof(err);
404  getsockopt(socket, SOL_SOCKET, SO_ERROR, (void *)&err, &errlen);
405  return -1;
406  }
407 #else // HAVE_EPOLL
408  // use zero-timeout select to check if async socket
409  fd_set wfds, exceptfds;
410  FD_ZERO(&wfds);
411  FD_ZERO(&exceptfds);
412  FD_SET(socket, &wfds);
413  FD_SET(socket, &exceptfds);
414  struct timeval tv;
415  tv.tv_sec = 0;
416  tv.tv_usec = 0;
417  int ret = select(socket + 1, NULL, &wfds, &exceptfds, &tv);
418  if (ret == -1)
419  {
420  // select() error
421  ROSCPP_CONN_LOG_DEBUG("select() on socket[%d] failed with error [%s]",
422  socket, last_socket_error_string());
423  err = last_socket_error();
424  return -1;
425  }
426  else if (ret == 0)
427  {
428  // select() timeout, socket is still connecting
429  err = 0;
430  return 0;
431  }
432  // select() fake wakeup?
433  ROS_ASSERT(FD_ISSET(socket, &wfds) || FD_ISSET(socket, &exceptfds));
434 
435  // check connection is success or failure
436 #ifdef WIN32
437  // In Windows:
438  // Success is reported in the writefds set and failure is reported in the exceptfds set.
439  // If failure, we must then call getsockopt SO_ERROR to determine the error value to describe why the failure occurred
440  ROS_ASSERT((FD_ISSET(socket, &wfds) && !FD_ISSET(socket, &exceptfds)) ||
441  (!FD_ISSET(socket, &wfds) && FD_ISSET(socket, &exceptfds)));
442  if (FD_ISSET(socket, &exceptfds)) {
443  // an error occurred during connection
444  int errinfo = 0;
445  socklen_t errlen = sizeof(int);
446  if (getsockopt(socket, SOL_SOCKET, SO_ERROR, reinterpret_cast<char*>(&errinfo), &errlen) == -1)
447  {
448  // getsockopt() error
449  ROSCPP_CONN_LOG_DEBUG("getsockopt() on socket[%d] failed with error [%s]",
450  socket, last_socket_error_string());
451  err = last_socket_error();
452  return -1;
453  }
454  ROS_ASSERT(errlen == sizeof(int));
455  ROS_ASSERT(errinfo != 0);
456  ROSCPP_CONN_LOG_DEBUG("Async connect on socket[%d] failed with error [%s]",
457  socket, socket_error_string(errinfo));
458  err = errinfo;
459  return -1;
460  }
461 #else // WIN32
462  // In Linux:
463  // Both success and failure is reported in the writefds set.
464  // We must use getsockopt() to read the SO_ERROR option at level SOL_SOCKET to determine whether
465  // connect() completed successfully (SO_ERROR is zero) or unsuccessfully (SO_ERROR is error code).
466  if (!FD_ISSET(socket, &wfds)) {
467  // wfds is not set, socket is still connecting
468  err = 0;
469  return 0;
470  }
471  // use getsockopt() to check connection is success or failure
472  int errinfo;
473  socklen_t errlen = sizeof(int);
474  if (getsockopt(socket, SOL_SOCKET, SO_ERROR, &errinfo, &errlen) == -1)
475  {
476  // getsockopt() error
477  ROSCPP_CONN_LOG_DEBUG("getsockopt() on socket[%d] failed with error [%s]",
478  socket, last_socket_error_string());
479  err = last_socket_error();
480  return -1;
481  }
482  ROS_ASSERT(errlen == sizeof(int));
483  if (errinfo != 0)
484  {
485  // an error occurred during connection
486  ROSCPP_CONN_LOG_DEBUG("Async connect on socket[%d] failed with error [%s]",
487  socket, socket_error_string(errinfo));
488  err = errinfo;
489  return -1;
490  }
491 #endif // WIN32
492 #endif // HAVE_EPOLL
493  err = 0;
494  return 1;
495 }
496 
501 int set_non_blocking(socket_fd_t &socket) {
502 #ifdef WIN32
503  u_long non_blocking = 1;
504  if(ioctlsocket( socket, FIONBIO, &non_blocking ) != 0 )
505  {
506  return WSAGetLastError();
507  }
508 #else
509  if(fcntl(socket, F_SETFL, O_NONBLOCK) == -1)
510  {
511  return errno;
512  }
513 #endif
514  return 0;
515 }
516 
522 int close_socket(socket_fd_t &socket) {
523 #ifdef WIN32
524  if(::closesocket(socket) == SOCKET_ERROR ) {
525  return -1;
526  } else {
527  return 0;
528  }
529 #else
530  if (::close(socket) < 0) {
531  return -1;
532  } else {
533  return 0;
534  }
535 #endif //WIN32
536 }
537 
538 /*****************************************************************************
539 ** Signal Pair
540 *****************************************************************************/
546 int create_signal_pair(signal_fd_t signal_pair[2]) {
547 #ifdef WIN32 // use a socket pair
548  signal_pair[0] = INVALID_SOCKET;
549  signal_pair[1] = INVALID_SOCKET;
550 
551  union {
552  struct sockaddr_in inaddr;
553  struct sockaddr addr;
554  } a;
555  socklen_t addrlen = sizeof(a.inaddr);
556 
557  /*********************
558  ** Listen Socket
559  **********************/
560  socket_fd_t listen_socket = INVALID_SOCKET;
561  listen_socket = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
562  if (listen_socket == INVALID_SOCKET) {
563  return -1;
564  }
565 
566  // allow it to be bound to an address already in use - do we actually need this?
567  int reuse = 1;
568  if (setsockopt(listen_socket, SOL_SOCKET, SO_REUSEADDR, (char*) &reuse, (socklen_t) sizeof(reuse)) == SOCKET_ERROR ) {
569  ::closesocket(listen_socket);
570  return -1;
571  }
572 
573  memset(&a, 0, sizeof(a));
574  a.inaddr.sin_family = AF_INET;
575  a.inaddr.sin_addr.s_addr = htonl(INADDR_LOOPBACK);
576  // For TCP/IP, if the port is specified as zero, the service provider assigns
577  // a unique port to the application from the dynamic client port range.
578  a.inaddr.sin_port = 0;
579 
580  if (bind(listen_socket, &a.addr, sizeof(a.inaddr)) == SOCKET_ERROR) {
581  ::closesocket(listen_socket);
582  return -1;
583  }
584  // we need this below because the system auto filled in some entries, e.g. port #
585  if (getsockname(listen_socket, &a.addr, &addrlen) == SOCKET_ERROR) {
586  ::closesocket(listen_socket);
587  return -1;
588  }
589  // max 1 connection permitted
590  if (listen(listen_socket, 1) == SOCKET_ERROR) {
591  ::closesocket(listen_socket);
592  return -1;
593  }
594 
595  /*********************
596  ** Connection
597  **********************/
598  // do we need io overlapping?
599  // DWORD flags = (make_overlapped ? WSA_FLAG_OVERLAPPED : 0);
600  DWORD overlapped_flag = 0;
601  signal_pair[0] = WSASocket(AF_INET, SOCK_STREAM, 0, NULL, 0, overlapped_flag);
602  if (signal_pair[0] == INVALID_SOCKET) {
603  ::closesocket(listen_socket);
604  ::closesocket(signal_pair[0]);
605  return -1;
606  }
607  // reusing the information from above to connect to the listener
608  if (connect(signal_pair[0], &a.addr, sizeof(a.inaddr)) == SOCKET_ERROR) {
609  ::closesocket(listen_socket);
610  ::closesocket(signal_pair[0]);
611  return -1;
612  }
613  /*********************
614  ** Accept
615  **********************/
616  signal_pair[1] = accept(listen_socket, NULL, NULL);
617  if (signal_pair[1] == INVALID_SOCKET) {
618  ::closesocket(listen_socket);
619  ::closesocket(signal_pair[0]);
620  ::closesocket(signal_pair[1]);
621  return -1;
622  }
623  /*********************
624  ** Nonblocking
625  **********************/
626  // should we do this or should we set io overlapping?
627  if ( (set_non_blocking(signal_pair[0]) != 0) || (set_non_blocking(signal_pair[1]) != 0) ) {
628  ::closesocket(listen_socket);
629  ::closesocket(signal_pair[0]);
630  ::closesocket(signal_pair[1]);
631  return -1;
632  }
633  /*********************
634  ** Cleanup
635  **********************/
636  ::closesocket(listen_socket); // the listener has done its job.
637  return 0;
638 #else // use a pipe pair
639  // initialize
640  signal_pair[0] = -1;
641  signal_pair[1] = -1;
642 
643  if(pipe(signal_pair) != 0) {
644  ROS_FATAL( "pipe() failed");
645  return -1;
646  }
647  if(fcntl(signal_pair[0], F_SETFL, O_NONBLOCK) == -1) {
648  ROS_FATAL( "fcntl() failed");
649  return -1;
650  }
651  if(fcntl(signal_pair[1], F_SETFL, O_NONBLOCK) == -1) {
652  ROS_FATAL( "fcntl() failed");
653  return -1;
654  }
655  return 0;
656 #endif // create_pipe
657 }
658 
659 } // namespace ros
ros::socket_error_string
const ROSCPP_DECL char * socket_error_string(int err)
Definition: io.cpp:78
ROSCPP_CONN_LOG_DEBUG
#define ROSCPP_CONN_LOG_DEBUG(...)
Definition: file_log.h:36
boost::shared_ptr
ros::create_signal_pair
ROSCPP_DECL int create_signal_pair(signal_fd_t signal_pair[2])
Definition: io.cpp:548
ros
ros::is_async_connected
ROSCPP_DECL int is_async_connected(socket_fd_t &socket, int &err)
Definition: io.cpp:384
ros::poll_sockets
ROSCPP_DECL pollfd_vector_ptr poll_sockets(int epfd, socket_pollfd *fds, nfds_t nfds, int timeout)
A cross platform polling function for sockets.
Definition: io.cpp:196
ros::close_socket_watcher
ROSCPP_DECL void close_socket_watcher(int fd)
Definition: io.cpp:126
ros::del_socket_from_watcher
ROSCPP_DECL void del_socket_from_watcher(int epfd, int fd)
Definition: io.cpp:149
ROS_FATAL
#define ROS_FATAL(...)
ros::last_socket_error_is_would_block
ROSCPP_DECL bool last_socket_error_is_would_block()
Definition: io.cpp:98
ros::add_socket_to_watcher
ROSCPP_DECL void add_socket_to_watcher(int epfd, int fd)
Definition: io.cpp:131
ros::socket_fd_t
int socket_fd_t
Definition: io.h:138
ros::last_socket_error
ROSCPP_DECL int last_socket_error()
Definition: io.cpp:70
ros::close_socket
ROSCPP_DECL int close_socket(socket_fd_t &socket)
Close the socket.
Definition: io.cpp:524
io.h
ROS_ERROR
#define ROS_ERROR(...)
ros::signal_fd_t
int signal_fd_t
Definition: io.h:139
ros::pollfd_vector_ptr
boost::shared_ptr< std::vector< socket_pollfd > > pollfd_vector_ptr
Definition: io.h:143
assert.h
ros::socket_pollfd
struct pollfd socket_pollfd
Definition: io.h:140
ros::set_events_on_socket
ROSCPP_DECL void set_events_on_socket(int epfd, int fd, int events)
Definition: io.cpp:161
UNUSED
#define UNUSED(expr)
Definition: io.cpp:60
ROS_ASSERT
#define ROS_ASSERT(cond)
ros::set_non_blocking
ROSCPP_DECL int set_non_blocking(socket_fd_t &socket)
Definition: io.cpp:503
ros::last_socket_error_string
const ROSCPP_DECL char * last_socket_error_string()
Definition: io.cpp:90
file_log.h
ros::create_socket_watcher
ROSCPP_DECL int create_socket_watcher()
Definition: io.cpp:114


roscpp
Author(s): Morgan Quigley, Josh Faust, Brian Gerkey, Troy Straszheim, Dirk Thomas , Jacob Perron
autogenerated on Thu Nov 23 2023 04:01:44