clamd/others.c
e3aaff8e
 /*
086eab5c
  *  Copyright (C) 2007-2009 Sourcefire, Inc.
  *
  *  Authors: Tomasz Kojm, Trog, Török Edvin
e3aaff8e
  *
  *  This program is free software; you can redistribute it and/or modify
bb34cb31
  *  it under the terms of the GNU General Public License version 2 as
  *  published by the Free Software Foundation.
e3aaff8e
  *
  *  This program is distributed in the hope that it will be useful,
  *  but WITHOUT ANY WARRANTY; without even the implied warranty of
  *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
  *  GNU General Public License for more details.
  *
  *  You should have received a copy of the GNU General Public License
  *  along with this program; if not, write to the Free Software
48b7b4a7
  *  Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
  *  MA 02110-1301, USA.
e3aaff8e
  */
 
98ac8d19
 #if HAVE_CONFIG_H
 #include "clamav-config.h"
 #endif
 
bfd89d7c
 /* must be first because it may define _XOPEN_SOURCE */
 #include "shared/fdpassing.h"
e3aaff8e
 #include <stdio.h>
 #include <stdarg.h>
 #include <stdlib.h>
 #include <string.h>
67118e92
 #ifdef	HAVE_UNISTD_H
e3aaff8e
 #include <unistd.h>
67118e92
 #endif
e3aaff8e
 #include <fcntl.h>
 #include <time.h>
 #include <sys/stat.h>
c5e7d5cd
 #include <errno.h>
e0bb54d7
 #ifndef	_WIN32
13d4160d
 #include <sys/time.h>
7bbd5f7f
 #include <sys/wait.h>
e0bb54d7
 #include <sys/socket.h>
 #include <sys/ioctl.h>
67118e92
 #endif
7bbd5f7f
 
d32fc9fe
 #if HAVE_SYS_PARAM_H
 #include <sys/param.h>
 #endif
 
fc83da82
 #ifdef HAVE_SYS_TYPES_H
13d4160d
 #include <sys/types.h>
 #endif
fc83da82
 #ifdef HAVE_SYS_FILIO_H
09a1f3a1
 #include <sys/filio.h>
 #endif
c5e7d5cd
 
1642ffba
 #include <pthread.h>
feeaa333
 
c5e7d5cd
 #if HAVE_POLL
 #if HAVE_POLL_H
 #include <poll.h>
 #else /* HAVE_POLL_H */
 #if HAVE_SYS_SELECT_H
 #include <sys/select.h>
 #endif /* HAVE_SYS_SELECT_H */
 #endif /* HAVE_POLL_H */
 #endif /* HAVE_POLL */
e3aaff8e
 
034994bd
 #include <limits.h>
064b4a0c
 #include "shared/optparser.h"
bd8603aa
 #include "shared/output.h"
3b074c78
 #include "shared/misc.h"
de5f850d
 #include "libclamav/others.h"
bd8603aa
 
7708ddfc
 #include "session.h"
079229d6
 #include "others.h"
034994bd
 
be4bf7f4
 #ifdef	_WIN32
064b4a0c
 void virusaction(const char *filename, const char *virname, const struct optstruct *opts)
67118e92
 {
064b4a0c
     if(optget(opts, "VirusEvent")->enabled)
67118e92
 	logg("^VirusEvent is not supported on this platform");	/* Yet */
 }
 
 #else
949c6fe5
 
 #define VE_FILENAME  "CLAM_VIRUSEVENT_FILENAME"
 #define VE_VIRUSNAME "CLAM_VIRUSEVENT_VIRUSNAME"
 
1642ffba
 static pthread_mutex_t virusaction_lock = PTHREAD_MUTEX_INITIALIZER;
 
064b4a0c
 void virusaction(const char *filename, const char *virname, const struct optstruct *opts)
0249f9d2
 {
557b40cc
 	pid_t pid;
064b4a0c
 	const struct optstruct *opt;
949c6fe5
 	char *buffer_file, *buffer_vir, *buffer_cmd;
 	const char *pt;
 	size_t i, j, v = 0, len;
751f8470
 	char *env[4];
0249f9d2
 
064b4a0c
 	if(!(opt = optget(opts, "VirusEvent"))->enabled)
751f8470
 		return;
0249f9d2
 
751f8470
 	env[0] = getenv("PATH");
 	j = env[0] ? 1 : 0;
 	/* Allocate env vars.. to be portable env vars should not be freed */
949c6fe5
 	buffer_file = (char *) malloc(strlen(VE_FILENAME) + strlen(filename) + 2);
751f8470
 	if(buffer_file) {
949c6fe5
 		sprintf(buffer_file, "%s=%s", VE_FILENAME, filename);
751f8470
 		env[j++] = buffer_file;
 	}
557b40cc
 
949c6fe5
 	buffer_vir = (char *) malloc(strlen(VE_VIRUSNAME) + strlen(virname) + 2);
751f8470
 	if(buffer_vir) {
949c6fe5
 		sprintf(buffer_vir, "%s=%s", VE_VIRUSNAME, virname);
751f8470
 		env[j++] = buffer_vir;
 	}
 	env[j++] = NULL;
0249f9d2
 
949c6fe5
 	pt = opt->strarg;
 	while((pt = strstr(pt, "%v"))) {
 	    pt += 2;
 	    v++;
29676a32
 	}
949c6fe5
 	len = strlen(opt->strarg);
 	buffer_cmd = (char *) calloc(len + v * strlen(virname) + 1, sizeof(char));
 	if(!buffer_cmd) {
 	    free(buffer_file);
 	    free(buffer_vir);
 	    return;
e357da7b
 	}
949c6fe5
 	for(i = 0, j = 0; i < len; i++) {
 	    if(i + 1 < len && opt->strarg[i] == '%' && opt->strarg[i + 1] == 'v') {
 		strcat(buffer_cmd, virname);
 		j += strlen(virname);
 		i++;
 	    } else {
 		buffer_cmd[j++] = opt->strarg[i];
 	    }
 	}
 
1642ffba
 	pthread_mutex_lock(&virusaction_lock);
751f8470
 	/* We can only call async-signal-safe functions after fork(). */
 	pid = fork();
949c6fe5
 	if(!pid) { /* child */
 	    exit(execle("/bin/sh", "sh", "-c", buffer_cmd, NULL, env));
 	} else if(pid > 0) { /* parent */
 	    pthread_mutex_unlock(&virusaction_lock);
 	    waitpid(pid, NULL, 0);
751f8470
 	} else {
949c6fe5
 	    logg("!VirusEvent: fork failed.\n");
29676a32
 	}
949c6fe5
 	free(buffer_cmd);
751f8470
 	free(buffer_file);
 	free(buffer_vir);
0249f9d2
 }
be4bf7f4
 #endif /* _WIN32 */
c5e7d5cd
 
949c6fe5
 /* Function: writen
 	Try hard to write the specified number of bytes
 */
 int writen(int fd, void *buff, unsigned int count)
c5e7d5cd
 {
 	int retval;
949c6fe5
 	unsigned int todo;
 	unsigned char *current;
  
     todo = count;
     current = (unsigned char *) buff;
  
     do {
 	retval = write(fd, current, todo);
 	if (retval < 0) {
 	    if (errno == EINTR) {
c5e7d5cd
 		continue;
 	    }
 	    return -1;
 	}
949c6fe5
 	todo -= retval;
 	current += retval;
     } while (todo > 0);
  
     return count;
 }
c5e7d5cd
 
949c6fe5
 static int realloc_polldata(struct fd_data *data)
 {
 #ifdef HAVE_POLL
     if (data->poll_data_nfds == data->nfds)
 	return 0;
     if (data->poll_data)
 	free(data->poll_data);
     data->poll_data = malloc(data->nfds*sizeof(*data->poll_data));
     if (!data->poll_data) {
 	logg("!realloc_polldata: Memory allocation failed for poll_data\n");
 	return -1;
c5e7d5cd
     }
949c6fe5
     data->poll_data_nfds = data->nfds;
c5e7d5cd
 #endif
949c6fe5
     return 0;
c5e7d5cd
 }
e0909dc6
 
f22f13d9
 int poll_fd(int fd, int timeout_sec, int check_signals)
57358cc8
 {
949c6fe5
     int ret;
7660b7cb
     struct fd_data fds = FDS_INIT(NULL);
949c6fe5
 
5f6edb22
     if (fds_add(&fds, fd, 1, timeout_sec) == -1)
949c6fe5
 	return -1;
5f6edb22
     do {
5eca45b5
 	ret = fds_poll_recv(&fds, timeout_sec, check_signals, NULL);
5f6edb22
     } while (ret == -1 && errno == EINTR);
949c6fe5
     fds_free(&fds);
     return ret;
 }
 
5f6edb22
 void fds_cleanup(struct fd_data *data)
949c6fe5
 {
     struct fd_buf *newbuf;
     unsigned i,j;
5f6edb22
 
949c6fe5
     for (i=0,j=0;i < data->nfds; i++) {
 	if (data->buf[i].fd < 0) {
 	    if (data->buf[i].buffer)
 		free(data->buf[i].buffer);
 	    continue;
 	}
 	if (i != j)
 	    data->buf[j] = data->buf[i];
 	j++;
     }
5f6edb22
     if (j == data->nfds)
 	return;
949c6fe5
     for (i = j ; i < data->nfds; i++)
 	data->buf[i].fd = -1;
     data->nfds = j;
ef49ed62
     logg("$Number of file descriptors polled: %u fds\n", (unsigned) data->nfds);
949c6fe5
     /* Shrink buffer */
     newbuf = realloc(data->buf, j*sizeof(*newbuf));
3d7d59c1
     if(!j)
 	data->buf = NULL;
     else if (newbuf)
949c6fe5
 	data->buf = newbuf;/* non-fatal if shrink fails */
57358cc8
 }
 
949c6fe5
 static int read_fd_data(struct fd_buf *buf)
e0909dc6
 {
949c6fe5
     ssize_t n;
e0909dc6
 
949c6fe5
     buf->got_newdata=1;
     if (!buf->buffer) /* listen-only socket */
 	return 1;
e0909dc6
 
949c6fe5
     if (buf->off >= buf->bufsize)
 	return -1;
e0909dc6
 
949c6fe5
    /* Read the pending packet, it may contain more than one command, but
     * that is to the cmdparser to handle. 
     * It will handle 1st command, and then move leftover to beginning of buffer
     */
 #ifdef HAVE_FD_PASSING
   {
       struct msghdr msg;
       struct cmsghdr *cmsg;
14896add
       union {
        unsigned char buff[CMSG_SPACE(sizeof(int))];
        struct cmsghdr hdr;
       } b;
949c6fe5
       struct iovec iov[1];
 
       if (buf->recvfd != -1) {
fb6fe4f5
 	  logg("$Closing unclaimed FD: %d\n", buf->recvfd);
949c6fe5
 	  close(buf->recvfd);
 	  buf->recvfd = -1;
       }
       memset(&msg, 0, sizeof(msg));
       iov[0].iov_base = buf->buffer + buf->off;
       iov[0].iov_len = buf->bufsize - buf->off;
       msg.msg_iov = iov;
       msg.msg_iovlen = 1;
14896add
       msg.msg_control = b.buff;
b0e57898
       msg.msg_controllen = sizeof(b.buff);
949c6fe5
 
       n = recvmsg(buf->fd, &msg, 0);
       if (n < 0)
 	  return -1;
cb9a7b44
       if (msg.msg_flags & MSG_TRUNC) {
e2fbea32
 	  logg("^Message truncated at %d bytes\n", (int)n);
cb9a7b44
 	  return -1;
       }
       if (msg.msg_flags & MSG_CTRUNC) {
 	  if (msg.msg_controllen > 0)
 	      logg("^Control message truncated at %d bytes, %d data read\n",
e2fbea32
 		   (int)msg.msg_controllen, (int)n);
cb9a7b44
 	  else
 	      logg("^Control message truncated, no control data received, %d bytes read"
 #ifdef C_LINUX
 		   "(Is SELinux/AppArmor enabled, and blocking file descriptor passing?)"
 #endif
 		   "\n",
e2fbea32
 		   (int)n);
949c6fe5
 	  return -1;
       }
4cc952e3
       if (msg.msg_controllen) {
 	  for (cmsg = CMSG_FIRSTHDR(&msg); cmsg != NULL;
 	       cmsg = CMSG_NXTHDR(&msg, cmsg)) {
 	      if (cmsg->cmsg_len == CMSG_LEN(sizeof(int)) &&
 		  cmsg->cmsg_level == SOL_SOCKET &&
 		  cmsg->cmsg_type == SCM_RIGHTS) {
 		  if (buf->recvfd != -1) {
625377ab
 		      logg("$Unclaimed file descriptor received. closing: %d\n", buf->recvfd);
4cc952e3
 		      close(buf->recvfd);
 		  }
 		  buf->recvfd = *(int *)CMSG_DATA(cmsg);
fb6fe4f5
 		  logg("$Receveived a file descriptor: %d\n", buf->recvfd);
949c6fe5
 	      }
 	  }
       }
   }
e0909dc6
 #else
949c6fe5
    n = recv(buf->fd, buf->buffer + buf->off, buf->bufsize - buf->off,0);
    if (n < 0)
        return -1;
 #endif
    buf->off += n;
    return n;
 }
e0909dc6
 
5f6edb22
 static int buf_init(struct fd_buf *buf, int listen_only, int timeout)
949c6fe5
 {
     buf->off = 0;
     buf->got_newdata = 0;
     buf->recvfd = -1;
     buf->mode = MODE_COMMAND;
     buf->id = 0;
     buf->dumpfd = -1;
     buf->chunksize = 0;
     buf->quota = 0;
     buf->dumpname = NULL;
     buf->group = NULL;
     buf->term = '\0';
     if (!listen_only) {
 	if (!buf->buffer) {
 	    buf->bufsize = PATH_MAX+8;
 	    /* plus extra space for a \0 so we can make sure every command is \0
 	     * terminated */
 	    if (!(buf->buffer = malloc(buf->bufsize + 1))) {
 		logg("!add_fd: Memory allocation failed for command buffer\n");
 		return -1;
 	    }
 	}
     } else {
 	if (buf->buffer)
 	    free(buf->buffer);
 	buf->bufsize = 0;
 	buf->buffer = NULL;
e0909dc6
     }
5f6edb22
     if (timeout) {
 	time(&buf->timeout_at);
 	buf->timeout_at += timeout;
     } else {
 	buf->timeout_at = 0;
     }
949c6fe5
     return 0;
 }
e0909dc6
 
5f6edb22
 int fds_add(struct fd_data *data, int fd, int listen_only, int timeout)
949c6fe5
 {
     struct fd_buf *buf;
     unsigned n;
     if (fd < 0) {
 	logg("!add_fd: invalid fd passed to add_fd\n");
 	return -1;
e0909dc6
     }
949c6fe5
     /* we may already have this fd, if
      * the old FD got closed, and the kernel reused the FD */
     for (n = 0; n < data->nfds; n++)
 	if (data->buf[n].fd == fd) {
 	    /* clear stale data in buffer */
5f6edb22
 	    if (buf_init(&data->buf[n], listen_only, timeout) < 0)
949c6fe5
 		return -1;
e0909dc6
 	    return 0;
 	}
949c6fe5
 
     n++;
     buf = realloc(data->buf, n*sizeof(*buf));
     if (!buf) {
 	logg("!add_fd: Memory allocation failed for fd_buf\n");
 	return -1;
e0909dc6
     }
949c6fe5
     data->buf = buf;
     data->nfds = n;
     data->buf[n-1].buffer = NULL;
5f6edb22
     if (buf_init(&data->buf[n-1], listen_only, timeout) < 0)
949c6fe5
 	return -1;
     data->buf[n-1].fd = fd;
     return 0;
e0909dc6
 }
94709323
 
7660b7cb
 static inline void fds_lock(struct fd_data *data)
 {
     if (data->buf_mutex)
 	pthread_mutex_lock(data->buf_mutex);
 }
 
 static inline void fds_unlock(struct fd_data *data)
 {
     if (data->buf_mutex)
 	pthread_mutex_unlock(data->buf_mutex);
 }
 
949c6fe5
 void fds_remove(struct fd_data *data, int fd)
94709323
 {
949c6fe5
     size_t i;
7660b7cb
     fds_lock(data);
949c6fe5
     if (data->buf) {
 	for (i=0;i<data->nfds;i++) {
 	    if (data->buf[i].fd == fd) {
 		data->buf[i].fd = -1;
 		break;
94709323
 	    }
 	}
949c6fe5
     }
7660b7cb
     fds_unlock(data);
94709323
 }
7708ddfc
 
949c6fe5
 #define BUFFSIZE 1024
 /* Wait till data is available to be read on any of the fds,
  * read available data on all fds, and mark them as appropriate.
  * One of the fds should be a pipe, used by the accept thread to wake us.
  * timeout is specified in seconds, if check_signals is non-zero, then
  * poll_recv_fds() will return upon receipt of a signal, even if no data
  * is received on any of the sockets.
7660b7cb
  * Must be called with buf_mutex lock held.
949c6fe5
  */
 /* TODO: handle ReadTimeout */
288b945f
 int fds_poll_recv(struct fd_data *data, int timeout, int check_signals, void *event)
7708ddfc
 {
949c6fe5
     unsigned fdsok = data->nfds;
     size_t i;
     int retval;
5f6edb22
     time_t now, closest_timeout;
949c6fe5
 
     /* we must have at least one fd, the control fd! */
5f6edb22
     fds_cleanup(data);
4155214d
 #ifndef _WIN32
949c6fe5
     if (!data->nfds)
 	return 0;
4155214d
 #endif
5f6edb22
     for (i=0;i < data->nfds;i++) {
949c6fe5
 	data->buf[i].got_newdata = 0;
5f6edb22
     }
 
e114b109
     time(&now);
5f6edb22
     if (timeout > 0)
 	closest_timeout = now + timeout;
     else
 	closest_timeout = 0;
     for (i=0;i < data->nfds; i++) {
 	time_t timeout_at = data->buf[i].timeout_at;
 	if (timeout_at && timeout_at < now) {
 	    /* timed out */
 	    data->buf[i].got_newdata = -2;
 	    /* we must return immediately from poll/select, we have a timeout! */
 	    closest_timeout = now;
 	} else {
 	    if (!closest_timeout)
 		closest_timeout = timeout_at;
f8e8ab4f
 	    else if (timeout_at && timeout_at < closest_timeout)
5f6edb22
 		closest_timeout = timeout_at;
 	}
     }
     if (closest_timeout)
 	timeout = closest_timeout - now;
     else
 	timeout = -1;
fb6fe4f5
     if (timeout > 0)
 	logg("$fds_poll_recv: timeout after %d seconds\n", timeout);
949c6fe5
 #ifdef HAVE_POLL
     /* Use poll() if available, preferred because:
      *  - can poll any number of FDs
      *  - can notify of both data available / socket disconnected events
      *  - when it says POLLIN it is guaranteed that a following recv() won't
      *  block (select may say that data is available to read, but a following 
      *  recv() may still block according to the manpage
      */
 
     if (realloc_polldata(data) == -1)
 	return -1;
     if (timeout > 0) {
 	/* seconds to ms */
 	timeout *= 1000;
     }
     for (i=0;i < data->nfds;i++) {
 	data->poll_data[i].fd = data->buf[i].fd;
 	data->poll_data[i].events = POLLIN;
 	data->poll_data[i].revents = 0;
     }
     do {
 	int n = data->nfds;
 
7660b7cb
 	fds_unlock(data);
5eca45b5
 #ifdef _WIN32
288b945f
 	retval = poll_with_event(data->poll_data, n, timeout, event);
5eca45b5
 #else
949c6fe5
 	retval = poll(data->poll_data, n, timeout);
5eca45b5
 #endif
7660b7cb
 	fds_lock(data);
949c6fe5
 
 	if (retval > 0) {
 	    fdsok = 0;
 	    /* nfds may change during poll, but not
 	     * poll_data_nfds */
 	    for (i=0;i < data->poll_data_nfds; i++) {
 		short revents;
 		if (data->buf[i].fd < 0)
442684f8
 		    continue;
949c6fe5
 		if (data->buf[i].fd != data->poll_data[i].fd) {
 		    /* should never happen */
 		    logg("!poll_recv_fds FD mismatch\n");
 		    continue;
 		}
 		revents = data->poll_data[i].revents;
 		if (revents & (POLLIN|POLLHUP)) {
fb6fe4f5
 		    logg("$Received POLLIN|POLLHUP on fd %d\n",data->poll_data[i].fd);
949c6fe5
 		}
ced85d63
 #ifndef _WIN32
c9954509
 		if (revents & POLLHUP) {
                        /* avoid SHUT_WR problem on Mac OS X */
                        int ret = send(data->poll_data[i].fd, &n, 0, 0);
                        if (!ret || (ret == -1 && errno == EINTR))
                                revents &= ~POLLHUP;
 		}
ced85d63
 #endif
949c6fe5
 		if (revents & POLLIN) {
 		    int ret = read_fd_data(&data->buf[i]);
 		    /* Data available to be read */
 		    if (ret == -1)
 			revents |= POLLERR;
 		    else if (!ret)
 			revents = POLLHUP;
 		}
 
 		if (revents & (POLLHUP | POLLERR | POLLNVAL)) {
 		    if (revents & (POLLHUP| POLLNVAL)) {
 			/* remote disconnected */
fb6fe4f5
 			logg("*Client disconnected (FD %d)\n",
949c6fe5
 			     data->poll_data[i].fd);
 		    } else {
 			/* error on file descriptor */
fb6fe4f5
 			logg("^Error condition on fd %d\n",
949c6fe5
 			     data->poll_data[i].fd);
 		    }
 		    data->buf[i].got_newdata = -1;
 		} else {
 		    fdsok++;
 		}
 	    }
442684f8
 	}
949c6fe5
     } while (retval == -1 && !check_signals && errno == EINTR);
 #else
3b074c78
     {
949c6fe5
     fd_set rfds;
     struct timeval tv;
     int maxfd = -1;
 
     for (i=0;i < data->nfds; i++) {
 	int fd = data->buf[i].fd;
 	if (fd >= FD_SETSIZE) {
fb6fe4f5
 	    logg ("!File descriptor is too high for FD_SET\n");
949c6fe5
 	    return -1;
442684f8
 	}
949c6fe5
 
 	maxfd = MAX(maxfd, fd);
442684f8
     }
949c6fe5
 
     do {
 	FD_ZERO(&rfds);
 	for(i=0;i < data->nfds;i++) {
 	    int fd = data->buf[i].fd;
 	    if (fd >= 0)
 		FD_SET(fd, &rfds);
 	}
 	tv.tv_sec = timeout;
 	tv.tv_usec = 0;
 
7660b7cb
 	fds_unlock(data);
0dd49673
 	retval = select(maxfd+1, &rfds, NULL, NULL, timeout >= 0 ? &tv : NULL);
7660b7cb
 	fds_lock(data);
949c6fe5
 	if (retval > 0) {
 	    fdsok = data->nfds;
 	    for (i=0; i < data->nfds; i++) {
 		if (data->buf[i].fd < 0) {
 		    fdsok--;
 		    continue;
 		}
 		if (FD_ISSET(data->buf[i].fd, &rfds)) {
 		    int ret = read_fd_data(&data->buf[i]);
 		    if (ret == -1 || !ret) {
 			if (ret == -1)
fb6fe4f5
 			    logg("!Error condition on fd %d\n",
949c6fe5
 				 data->buf[i].fd);
c9954509
 			else {
 			    /* avoid SHUT_WR problem on Mac OS X */
0dd49673
 			    int ret = send(data->buf[i].fd, &i, 0, 0);
c9954509
 			    if (!ret || (ret == -1 && errno == EINTR))
 				continue;
fb6fe4f5
 			    logg("*Client disconnected\n");
c9954509
 			}
949c6fe5
 			data->buf[i].got_newdata = -1;
 		    }
 		}
442684f8
 	    }
 	}
949c6fe5
 	if (retval < 0 && errno == EBADF) {
 	    /* unlike poll(),  select() won't tell us which FD is bad, so
 	     * we have to check them one by one. */
 	    tv.tv_sec = 0;
 	    tv.tv_usec = 0;
 	    /* with tv == 0 it doesn't check for EBADF */
 	    FD_ZERO(&rfds);
 	    for (i=0; i< data->nfds; i++) {
 		if (data->buf[i].fd == -1)
 		    continue;
 		FD_SET(data->buf[i].fd, &rfds);
 		do {
 		    retval = select(data->buf[i].fd+1, &rfds, NULL, NULL, &tv);
 		} while (retval == -1 && errno == EINTR);
 		if (retval == -1) {
 		    data->buf[i].fd = -1;
 		} else {
 		    FD_CLR(data->buf[i].fd, &rfds);
 		}
442684f8
 	    }
949c6fe5
 	    retval = -1;
 	    errno = EINTR;
 	    continue;
442684f8
 	}
949c6fe5
     } while (retval == -1 && !check_signals && errno == EINTR);
3b074c78
     }
949c6fe5
 #endif
 
     if (retval == -1 && errno != EINTR) {
e68d70e7
 	char err[128];
949c6fe5
 #ifdef HAVE_POLL
e68d70e7
 	logg("!poll_recv_fds: poll failed: %s\n", cli_strerror(errno, err, sizeof(err)));
949c6fe5
 #else
e68d70e7
 	logg("!poll_recv_fds: select failed: %s\n", cli_strerror(errno, err, sizeof(err)));
949c6fe5
 #endif
442684f8
     }
949c6fe5
 
     return retval;
 }
 
 void fds_free(struct fd_data *data)
 {
     unsigned i;
7660b7cb
     fds_lock(data);
949c6fe5
     for (i=0;i < data->nfds;i++) {
 	if (data->buf[i].buffer) {
 	    free(data->buf[i].buffer);
442684f8
 	}
     }
949c6fe5
     if (data->buf)
 	free(data->buf);
 #ifdef HAVE_POLL
     if (data->poll_data)
 	free(data->poll_data);
 #endif
     data->buf = NULL;
     data->nfds = 0;
7660b7cb
     fds_unlock(data);
7708ddfc
 }