diff --git a/common/common.c b/common/common.c index 2dd46774dc..838ef58c89 100644 --- a/common/common.c +++ b/common/common.c @@ -5174,6 +5174,53 @@ char *xstrdup(const char *string) return p; } +/* Try to connect to addr, using select() for timeout since AF_UNIX won't timeout normally */ +#ifndef WIN32 +int select_connect(int fd, const struct sockaddr_un *addr, size_t addrlen, const time_t d_sec, const suseconds_t d_usec) +{ + int rc; + int err = 0; + int flags = fcntl(fd, F_GETFL, 0); + fcntl(fd, F_SETFL, flags | O_NONBLOCK); + + rc = connect(fd, (const struct sockaddr *)addr, addrlen); + if (rc == -1 && errno != EINPROGRESS) { + err = errno; + } else if (rc == -1) { + fd_set w_fds; + fd_set e_fds; + struct timeval tv; + + FD_ZERO(&w_fds); + FD_SET(fd, &w_fds); + FD_ZERO(&e_fds); + FD_SET(fd, &e_fds); + + tv.tv_sec = d_sec; + tv.tv_usec = d_usec; + + rc = select(fd + 1, NULL, &w_fds, &e_fds, &tv); + + if (rc < 0) { + err = errno; + } else if (rc == 0) { + err = ETIMEDOUT; + } else { + socklen_t len = sizeof(err); + getsockopt(fd, SOL_SOCKET, SO_ERROR, &err, &len); + } + } + + fcntl(fd, F_SETFL, flags); + + if (err != 0) { + errno = err; + return -1; + } + return 0; +} +#endif + /* Read up to buflen bytes from fd and return the number of bytes read. If no data is available within d_sec + d_usec, return 0. On error, a value < 0 is returned (errno indicates error). */ diff --git a/drivers/upsdrvquery.c b/drivers/upsdrvquery.c index a105e53624..53a5655010 100644 --- a/drivers/upsdrvquery.c +++ b/drivers/upsdrvquery.c @@ -81,7 +81,7 @@ udq_pipe_conn_t *upsdrvquery_connect(const char *sockfn) { return NULL; } - if (connect(conn->sockfd, (struct sockaddr *) &sa, sizeof(sa)) < 0) { + if (select_connect(conn->sockfd, &sa, sizeof(sa), 1, 0) < 0) { if (nut_debug_level > 0 || nut_upsdrvquery_debug_level >= NUT_UPSDRVQUERY_DEBUG_LEVEL_CONNECT) upslog_with_errno(LOG_ERR, "connect to driver socket at %s", sockfn); close(conn->sockfd); diff --git a/include/common.h b/include/common.h index d493ac7565..b50c14a0a3 100644 --- a/include/common.h +++ b/include/common.h @@ -67,6 +67,7 @@ #ifndef WIN32 # include +# include #else /* WIN32 */ # include # include @@ -754,6 +755,7 @@ int match_regex_hex(const regex_t *preg, const int n); /* Note: different method signatures instead of TYPE_FD_SER due to "const" */ #ifndef WIN32 +int select_connect(int fd, const struct sockaddr_un *addr, size_t addrlen, const time_t d_sec, const suseconds_t d_usec); ssize_t select_read(const int fd, void *buf, const size_t buflen, const time_t d_sec, const suseconds_t d_usec); ssize_t select_write(const int fd, const void *buf, const size_t buflen, const time_t d_sec, const suseconds_t d_usec); #else /* WIN32 */ diff --git a/server/sstate.c b/server/sstate.c index 9bae0cd397..91f233f6b5 100644 --- a/server/sstate.c +++ b/server/sstate.c @@ -215,9 +215,10 @@ TYPE_FD sstate_connect(upstype_t *ups) return ERROR_FD; } - ret = connect(fd, (struct sockaddr *) &sa, sizeof(sa)); + ret = select_connect(fd, &sa, sizeof(sa), 1, 0); if (ret < 0) { + int err = errno; time_t now; if (strstr(sa.sun_path, "/")) { @@ -237,6 +238,8 @@ TYPE_FD sstate_connect(upstype_t *ups) return ERROR_FD; ups->last_connfail = now; + /* restore errno for logging */ + errno = err; if (strstr(ups->fn, "/")) { upslog_with_errno(LOG_ERR, "Can't connect to UPS [%s] (%s)", ups->name, ups->fn);