clean up noise
[debian/amanda] / common-src / util.c
index 0eafec0b74d6822f7305e453b69c498eac97f789..bb27cfac0618d1af986bd06eae9a231d61111a0e 100644 (file)
  * file named AUTHORS, in the root directory of this distribution.
  */
 /*
- * $Id: util.c,v 1.17 2006/01/14 04:37:19 paddy_s Exp $
+ * $Id: util.c,v 1.42 2006/08/24 01:57:15 paddy_s Exp $
  */
 
 #include "amanda.h"
 #include "util.h"
+#include "arglist.h"
+#include "clock.h"
+
+/*#define NET_READ_DEBUG*/
+
+#ifdef NET_READ_DEBUG
+#define netprintf(x)    dbprintf(x)
+#else
+#define netprintf(x)
+#endif
+
+static int make_socket(sa_family_t family);
+static int connect_port(struct sockaddr_storage *addrp, in_port_t port, char *proto,
+                       struct sockaddr_storage *svaddr, int nonblock);
 
 /*
  * Keep calling read() until we've read buflen's worth of data, or EOF,
  * Returns the number of bytes read, 0 on EOF, or negative on error.
  */
 ssize_t
-fullread(fd, vbuf, buflen)
-    int fd;
-    void *vbuf;
-    size_t buflen;
+fullread(
+    int                fd,
+    void *     vbuf,
+    size_t     buflen)
 {
     ssize_t nread, tot = 0;
     char *buf = vbuf;  /* cast to char so we can ++ it */
@@ -70,10 +84,10 @@ fullread(fd, vbuf, buflen)
  * Returns the number of bytes written, or negative on error.
  */
 ssize_t
-fullwrite(fd, vbuf, buflen)
-    int fd;
-    const void *vbuf;
-    size_t buflen;
+fullwrite(
+    int                fd,
+    const void *vbuf,
+    size_t     buflen)
 {
     ssize_t nwritten, tot = 0;
     const char *buf = vbuf;    /* cast to char so we can ++ it */
@@ -92,6 +106,207 @@ fullwrite(fd, vbuf, buflen)
     return (tot);
 }
 
+static int
+make_socket(
+    sa_family_t family)
+{
+    int s;
+    int save_errno;
+#if defined(SO_KEEPALIVE) || defined(USE_REUSEADDR)
+    int on=1;
+    int r;
+#endif
+
+    s = socket(family, SOCK_STREAM, 0);
+    if (s == -1) {
+        save_errno = errno;
+        dbprintf(("%s: make_socket: socket() failed: %s\n",
+                  debug_prefix_time(NULL),
+                  strerror(save_errno)));
+        errno = save_errno;
+        return -1;
+    }
+    if (s < 0 || s >= (int)FD_SETSIZE) {
+        aclose(s);
+        errno = EMFILE;                         /* out of range */
+        return -1;
+    }
+
+#ifdef USE_REUSEADDR
+    r = setsockopt(s, SOL_SOCKET, SO_REUSEADDR, &on, sizeof(on));
+    if (r < 0) {
+       save_errno = errno;
+       dbprintf(("%s: stream_server: setsockopt(SO_REUSEADDR) failed: %s\n",
+                 debug_prefix_time(NULL),
+                 strerror(errno)));
+       errno = save_errno;
+    }
+#endif
+
+#ifdef SO_KEEPALIVE
+    r = setsockopt(s, SOL_SOCKET, SO_KEEPALIVE,
+                  (void *)&on, SIZEOF(on));
+    if (r == -1) {
+       save_errno = errno;
+       dbprintf(("%s: make_socket: setsockopt() failed: %s\n",
+                  debug_prefix_time(NULL),
+                  strerror(save_errno)));
+       aclose(s);
+       errno = save_errno;
+       return -1;
+    }
+#endif
+
+    return s;
+}
+
+/* addrp is my address */
+/* svaddr is the address of the remote machine */
+/* return socket on success */
+/* return -1     on failure */
+int
+connect_portrange(
+    struct sockaddr_storage *addrp,
+    in_port_t          first_port,
+    in_port_t          last_port,
+    char *             proto,
+    struct sockaddr_storage *svaddr,
+    int                        nonblock)
+{
+    int                        s;
+    in_port_t          port;
+    static in_port_t   port_in_use[1024];
+    static int         nb_port_in_use = 0;
+    int                        i;
+
+    assert(first_port <= last_port);
+    /* Try a port already used */
+    for(i=0; i < nb_port_in_use; i++) {
+       port = port_in_use[i];
+       if(port >= first_port && port <= last_port) {
+           s = connect_port(addrp, port, proto, svaddr, nonblock);
+           if(s == -2) return -1;
+           if(s > 0) {
+               return s;
+           }
+       }
+    }
+
+    /* Try a port in the range */
+    for (port = first_port; port <= last_port; port++) {
+       s = connect_port(addrp, port, proto, svaddr, nonblock);
+       if(s == -2) return -1;
+       if(s > 0) {
+           port_in_use[nb_port_in_use++] = port;
+           return s;
+       }
+    }
+
+    dbprintf(("%s: connect_portrange: all ports between %d and %d busy\n",
+             debug_prefix_time(NULL),
+             first_port,
+             last_port));
+    errno = EAGAIN;
+    return -1;
+}
+
+/* addrp is my address */
+/* svaddr is the address of the remote machine */
+/* return -2: Don't try again */
+/* return -1: Try with another port */
+/* return >0: this is the connected socket */
+int
+connect_port(
+    struct sockaddr_storage *addrp,
+    in_port_t                  port,
+    char *             proto,
+    struct sockaddr_storage *svaddr,
+    int                        nonblock)
+{
+    int                        save_errno;
+    struct servent *   servPort;
+    socklen_t          len;
+    socklen_t          socklen;
+    int                        s;
+
+    servPort = getservbyport((int)htons(port), proto);
+    if (servPort != NULL && !strstr(servPort->s_name, "amanda")) {
+       dbprintf(("%s: connect_port: Skip port %d: Owned by %s.\n",
+                 debug_prefix_time(NULL), port, servPort->s_name));
+       return -1;
+    }
+
+    if(servPort == NULL)
+       dbprintf(("%s: connect_port: Try  port %d: Available   - \n",
+                 debug_prefix_time(NULL), port));
+    else {
+       dbprintf(("%s: connect_port: Try  port %d: Owned by %s - \n",
+                 debug_prefix_time(NULL), port, servPort->s_name));
+    }
+
+    if ((s = make_socket(addrp->ss_family)) == -1) return -2;
+
+    SS_SET_PORT(addrp, port);
+    socklen = SS_LEN(addrp);
+    if (bind(s, (struct sockaddr *)addrp, socklen) != 0) {
+       save_errno = errno;
+       aclose(s);
+       if (save_errno != EADDRINUSE) {
+           dbprintf(("errno %d strerror %s\n",
+                     errno, strerror(errno)));
+           errno = save_errno;
+           return -2;
+       }
+       errno = save_errno;
+       return -1;
+    }
+
+    /* find out what port was actually used */
+
+    len = sizeof(*addrp);
+    if (getsockname(s, (struct sockaddr *)addrp, &len) == -1) {
+       save_errno = errno;
+       dbprintf(("%s: connect_port: getsockname() failed: %s\n",
+                 debug_prefix_time(NULL),
+                 strerror(save_errno)));
+       aclose(s);
+       errno = save_errno;
+       return -1;
+    }
+
+    if (nonblock)
+       fcntl(s, F_SETFL, fcntl(s, F_GETFL, 0)|O_NONBLOCK);
+    if (connect(s, (struct sockaddr *)svaddr, SS_LEN(svaddr)) == -1 && !nonblock) {
+       save_errno = errno;
+       dbprintf(("%s: connect_portrange: connect from %s failed: %s\n",
+                 debug_prefix_time(NULL),
+                 str_sockaddr(addrp),
+                 strerror(save_errno)));
+       dbprintf(("%s: connect_portrange: connect to %s failed: %s\n",
+                 debug_prefix_time(NULL),
+                 str_sockaddr(svaddr),
+                 strerror(save_errno)));
+       aclose(s);
+       errno = save_errno;
+       if (save_errno == ECONNREFUSED ||
+           save_errno == EHOSTUNREACH ||
+           save_errno == ENETUNREACH ||
+           save_errno == ETIMEDOUT)  {
+           return -2   ;
+       }
+       return -1;
+    }
+
+    dbprintf(("%s: connected to %s\n",
+              debug_prefix_time(NULL),
+              str_sockaddr(svaddr)));
+    dbprintf(("%s: our side is %s\n",
+              debug_prefix_time(NULL),
+              str_sockaddr(addrp)));
+    return s;
+}
+
+
 /*
  * Bind to a port in the given range.  Takes a begin,end pair of port numbers.
  *
@@ -99,67 +314,61 @@ fullwrite(fd, vbuf, buflen)
  * on success.
  */
 int
-bind_portrange(s, addrp, first_port, last_port, proto)
-    int s;
-    struct sockaddr_in *addrp;
-    int first_port, last_port;
-    char *proto;
+bind_portrange(
+    int                        s,
+    struct sockaddr_storage *addrp,
+    in_port_t          first_port,
+    in_port_t          last_port,
+    char *             proto)
 {
-    int port, cnt;
-    const int num_ports = last_port - first_port + 1;
-    int save_errno;
+    in_port_t port;
+    in_port_t cnt;
+    socklen_t socklen;
     struct servent *servPort;
+    const in_port_t num_ports = (in_port_t)(last_port - first_port + 1);
 
-    assert(first_port > 0 && first_port <= last_port && last_port < 65536);
+    assert(first_port <= last_port);
 
     /*
      * We pick a different starting port based on our pid and the current
      * time to avoid always picking the same reserved port twice.
      */
-    port = ((getpid() + time(0)) % num_ports) + first_port;
+    port = (in_port_t)(((getpid() + time(0)) % num_ports) + first_port);
+
     /*
      * Scan through the range, trying all available ports that are either 
      * not taken in /etc/services or registered for *amanda*.  Wrap around
      * if we don't happen to start at the beginning.
      */
     for (cnt = 0; cnt < num_ports; cnt++) {
-       servPort = getservbyport(htons(port), proto);
-       if((servPort == NULL) || strstr(servPort->s_name, "amanda")){
-           dbprintf(("%s: bind_portrange2: trying port=%d\n",
+       servPort = getservbyport((int)htons(port), proto);
+       if ((servPort == NULL) || strstr(servPort->s_name, "amanda")) {
+           if (servPort == NULL) {
+               dbprintf(("%s: bind_portrange2: Try  port %d: Available   - ",
                      debug_prefix_time(NULL), port));
-           addrp->sin_port = htons(port);
-           if (bind(s, (struct sockaddr *)addrp, sizeof(*addrp)) >= 0)
+           } else {
+               dbprintf(("%s: bind_portrange2: Try  port %d: Owned by %s - ",
+                     debug_prefix_time(NULL), port, servPort->s_name));
+           }
+           SS_SET_PORT(addrp, port);
+           socklen = SS_LEN(addrp);
+           if (bind(s, (struct sockaddr *)addrp, socklen) >= 0) {
+               dbprintf(("Success\n"));
                return 0;
-           /*
-            * If the error was something other then port in use, stop.
-            */
-           if (errno != EADDRINUSE)
-               break;
+           }
+           dbprintf(("%s\n", strerror(errno)));
+       } else {
+               dbprintf(("%s: bind_portrange2: Skip port %d: Owned by %s.\n",
+                     debug_prefix_time(NULL), port, servPort->s_name));
        }
        if (++port > last_port)
            port = first_port;
     }
-    if (cnt == num_ports) {
-       dbprintf(("%s: bind_portrange: all ports between %d and %d busy\n",
+    dbprintf(("%s: bind_portrange: all ports between %d and %d busy\n",
                  debug_prefix_time(NULL),
                  first_port,
                  last_port));
-       errno = EAGAIN;
-    } else if (last_port < IPPORT_RESERVED
-              && getuid() != 0
-              && errno == EACCES) {
-       /*
-        * Do not bother with an error message in this case because it
-        * is expected.
-        */
-    } else {
-       save_errno = errno;
-       dbprintf(("%s: bind_portrange: port %d: %s\n",
-                 debug_prefix_time(NULL),
-                 port,
-                 strerror(errno)));
-       errno = save_errno;
-    }
+    errno = EAGAIN;
     return -1;
 }
 
@@ -167,20 +376,23 @@ bind_portrange(s, addrp, first_port, last_port, proto)
  * Construct a datestamp (YYYYMMDD) from a time_t.
  */
 char *
-construct_datestamp(t)
-    time_t *t;
+construct_datestamp(
+    time_t *t)
 {
     struct tm *tm;
-    char datestamp[3*NUM_STR_SIZE];
+    char datestamp[3 * NUM_STR_SIZE];
     time_t when;
 
-    if(t == NULL) {
+    if (t == NULL) {
        when = time((time_t *)NULL);
     } else {
        when = *t;
     }
     tm = localtime(&when);
-    snprintf(datestamp, sizeof(datestamp),
+    if (!tm)
+       return stralloc("19000101");
+
+    snprintf(datestamp, SIZEOF(datestamp),
              "%04d%02d%02d", tm->tm_year+1900, tm->tm_mon+1, tm->tm_mday);
     return stralloc(datestamp);
 }
@@ -189,22 +401,429 @@ construct_datestamp(t)
  * Construct a timestamp (YYYYMMDDHHMMSS) from a time_t.
  */
 char *
-construct_timestamp(t)
-    time_t *t;
+construct_timestamp(
+    time_t *t)
 {
     struct tm *tm;
-    char timestamp[6*NUM_STR_SIZE];
+    char timestamp[6 * NUM_STR_SIZE];
     time_t when;
 
-    if(t == NULL) {
+    if (t == NULL) {
        when = time((time_t *)NULL);
     } else {
        when = *t;
     }
     tm = localtime(&when);
-    snprintf(timestamp, sizeof(timestamp),
+    if (!tm)
+       return stralloc("19000101000000");
+
+    snprintf(timestamp, SIZEOF(timestamp),
             "%04d%02d%02d%02d%02d%02d",
             tm->tm_year+1900, tm->tm_mon+1, tm->tm_mday,
             tm->tm_hour, tm->tm_min, tm->tm_sec);
     return stralloc(timestamp);
 }
+
+
+int
+needs_quotes(
+    const char * str)
+{
+    return (match("[ \t\f\r\n\"]", str) != 0);
+}
+
+
+/*
+ * For backward compatibility we are trying for minimal quoting.
+ * We only quote a string if it contains whitespace or is misquoted...
+ */
+
+char *
+quote_string(
+    const char *str)
+{
+    char *  s;
+    char *  ret;
+
+    if ((str == NULL) || (*str == '\0')) {
+       ret = stralloc("\"\"");
+    } else if ((match("[\\\"[:space:][:cntrl:]]", str)) == 0) {
+       /*
+        * String does not need to be quoted since it contains
+        * neither whitespace, control or quote characters.
+        */
+       ret = stralloc(str);
+    } else {
+       /*
+        * Allocate maximum possible string length.
+        * (a string of all quotes plus room for leading ", trailing " and NULL)
+        */
+       ret = s = alloc((strlen(str) * 2) + 2 + 1);
+       *(s++) = '"';
+       while (*str != '\0') {
+            if (*str == '\t') {
+                *(s++) = '\\';
+                *(s++) = 't';
+               str++;
+               continue;
+           } else if (*str == '\n') {
+                *(s++) = '\\';
+                *(s++) = 'n';
+               str++;
+               continue;
+           } else if (*str == '\r') {
+                *(s++) = '\\';
+                *(s++) = 'r';
+               str++;
+               continue;
+           } else if (*str == '\f') {
+                *(s++) = '\\';
+                *(s++) = 'f';
+               str++;
+               continue;
+           } else if (*str == '\\') {
+                *(s++) = '\\';
+                *(s++) = '\\';
+               str++;
+               continue;
+           }
+            if (*str == '"')
+                *(s++) = '\\';
+            *(s++) = *(str++);
+        }
+        *(s++) = '"';
+        *s = '\0';
+    }
+    return (ret);
+}
+
+
+char *
+unquote_string(
+    const char *str)
+{
+    char * ret;
+
+    if ((str == NULL) || (*str == '\0')) {
+       ret = stralloc("");
+    } else {
+       char * in;
+       char * out;
+
+       ret = in = out = stralloc(str);
+       while (*in != '\0') {
+           if (*in == '"') {
+               in++;
+               continue;
+           }
+
+           if (*in == '\\') {
+               in++;
+               if (*in == 'n') {
+                   in++;
+                   *(out++) = '\n';
+                   continue;
+               } else if (*in == 't') {
+                   in++;
+                   *(out++) = '\t';
+                   continue;
+               } else if (*in == 'r') {
+                   in++;
+                   *(out++) = '\r';
+                   continue;
+               } else if (*in == 'f') {
+                   in++;
+                   *(out++) = '\f';
+                   continue;
+               }
+           }
+           *(out++) = *(in++);
+       }
+        *out = '\0';
+    }
+    return (ret);
+}
+
+char *
+sanitize_string(
+    const char *str)
+{
+    char * s;
+    char * ret;
+
+    if ((str == NULL) || (*str == '\0')) {
+       ret = stralloc("");
+    } else {
+       ret = stralloc(str);
+       for (s = ret; *s != '\0'; s++) {
+           if (iscntrl(*s))
+               *s = '?';
+       }
+    }
+    return (ret);
+}
+
+char *
+strquotedstr(void)
+{
+    char *  tok = strtok(NULL, " ");
+
+    if ((tok != NULL) && (tok[0] == '"')) {
+       char *  t;
+       size_t  len;
+
+        len = strlen(tok);
+       do {
+           t = strtok(NULL, " ");
+           tok[len] = ' ';
+           len = strlen(tok);
+       } while ((t != NULL) &&
+                (tok[len - 1] != '"') && (tok[len - 2] != '\\'));
+    }
+    return tok;
+}
+
+ssize_t
+hexdump(
+    const char *buffer,
+    size_t     len)
+{
+    ssize_t rc = -1;
+
+    FILE *stream = popen("od -c -x -", "w");
+       
+    if (stream != NULL) {
+       fflush(stdout);
+       rc = (ssize_t)fwrite(buffer, len, 1, stream);
+       if (ferror(stream))
+           rc = -1;
+       pclose(stream);
+    }
+    return rc;
+}
+
+/*
+   Return 0 if the following characters are present
+   * ( ) < > [ ] , ; : ! $ \ / "
+   else returns 1
+*/
+
+int
+validate_mailto(
+    const char *mailto)
+{
+    return !match("\\*|<|>|\\(|\\)|\\[|\\]|,|;|:|\\\\|/|\"|\\!|\\$|\\|", mailto);
+}
+
+
+void
+dump_sockaddr(
+    struct sockaddr_storage *sa)
+{
+#ifdef WORKING_IPV6
+    char ipstr[INET6_ADDRSTRLEN];
+#else
+    char ipstr[INET_ADDRSTRLEN];
+#endif
+    int port;
+
+    port = SS_GET_PORT(sa);
+#ifdef WORKING_IPV6
+    if ( sa->ss_family == (sa_family_t)AF_INET6) {
+       inet_ntop(AF_INET6, &((struct sockaddr_in6 *)sa)->sin6_addr,
+                 ipstr, sizeof(ipstr));
+       dbprintf(("%s: (sockaddr_in6 *)%p = { %d, %d, %s }\n",
+                 debug_prefix_time(NULL), sa,
+                 ((struct sockaddr_in6 *)sa)->sin6_family,
+                 port,
+                 ipstr));
+    } else
+#endif
+    {
+       inet_ntop(AF_INET, &((struct sockaddr_in *)sa)->sin_addr, ipstr,
+                 sizeof(ipstr));
+       dbprintf(("%s: (sockaddr_in *)%p = { %d, %d, %s }\n",
+                 debug_prefix_time(NULL), sa,
+                 ((struct sockaddr_in *)sa)->sin_family,
+                 port,
+                 ipstr));
+    }
+}
+
+
+#ifdef WORKING_IPV6
+static char mystr_sockaddr[INET6_ADDRSTRLEN + 20];
+#else
+static char mystr_sockaddr[INET_ADDRSTRLEN + 20];
+#endif
+
+char *
+str_sockaddr(
+    struct sockaddr_storage *sa)
+{
+#ifdef WORKING_IPV6
+    char ipstr[INET6_ADDRSTRLEN];
+#else
+    char ipstr[INET_ADDRSTRLEN];
+#endif
+    int port;
+
+    port = SS_GET_PORT(sa);
+#ifdef WORKING_IPV6
+    if ( sa->ss_family == (sa_family_t)AF_INET6) {
+       inet_ntop(AF_INET6, &((struct sockaddr_in6 *)sa)->sin6_addr,
+                 ipstr, sizeof(ipstr));
+    } else
+#endif
+    {
+       inet_ntop(AF_INET, &((struct sockaddr_in *)sa)->sin_addr, ipstr,
+                 sizeof(ipstr));
+    }
+    snprintf(mystr_sockaddr,sizeof(mystr_sockaddr),"%s.%d", ipstr, port);
+    return mystr_sockaddr;
+}
+
+
+int
+cmp_sockaddr(
+    struct sockaddr_storage *ss1,
+    struct sockaddr_storage *ss2,
+    int addr_only)
+{
+    /* if addresses are v4mapped, "unmap" them */
+#ifdef WORKING_IPV6
+#ifdef IN6_IS_ADDR_V4MAPPED
+    struct sockaddr_in ss1_v4;
+    struct sockaddr_in ss2_v4;
+
+    if (ss1->ss_family == AF_INET6 &&
+        IN6_IS_ADDR_V4MAPPED(&((struct sockaddr_in6 *)ss1)->sin6_addr)) {
+       memset(&ss1_v4, 0, sizeof(struct sockaddr_in));
+       memcpy(&ss1_v4.sin_addr.s_addr,
+              &(((struct sockaddr_in6 *)ss1)->sin6_addr.s6_addr[12]),
+              sizeof(struct in_addr));
+       ss1_v4.sin_family = AF_INET;
+       SS_SET_PORT((struct sockaddr_storage *)&ss1_v4, SS_GET_PORT(ss1));
+       ss1 = (struct sockaddr_storage *)&ss1_v4;
+    }
+
+    if (ss2->ss_family == AF_INET6 &&
+        IN6_IS_ADDR_V4MAPPED(&((struct sockaddr_in6 *)ss2)->sin6_addr)) {
+       memset(&ss2_v4, 0, sizeof(struct sockaddr_in));
+       memcpy(&ss2_v4.sin_addr.s_addr,
+              &(((struct sockaddr_in6 *)ss2)->sin6_addr.s6_addr[12]),
+              sizeof(struct in_addr));
+       ss2_v4.sin_family = AF_INET;
+       SS_SET_PORT((struct sockaddr_storage *)&ss2_v4, SS_GET_PORT(ss2));
+       ss2 = (struct sockaddr_storage *)&ss2_v4;
+    }
+#endif
+#endif
+
+    if (ss1->ss_family == ss2->ss_family) {
+        if (addr_only) {
+#ifdef WORKING_IPV6
+            if(ss1->ss_family == (sa_family_t)AF_INET6)
+                return memcmp(
+                    &((struct sockaddr_in6 *)ss1)->sin6_addr,
+                    &((struct sockaddr_in6 *)ss2)->sin6_addr,
+                    sizeof(((struct sockaddr_in6 *)ss1)->sin6_addr));
+            else
+#endif
+                return memcmp(
+                    &((struct sockaddr_in *)ss1)->sin_addr,
+                    &((struct sockaddr_in *)ss2)->sin_addr,
+                    sizeof(((struct sockaddr_in *)ss1)->sin_addr));
+        } else {
+            return memcmp(ss1, ss2, SS_LEN(ss1));
+        }
+    } else {
+        /* compare families to give a total order */
+        if (ss1->ss_family < ss2->ss_family)
+            return -1;
+        else
+            return 1;
+    }
+}
+
+
+int copy_file(
+    char  *dst,
+    char  *src,
+    char **errmsg)
+{
+    int     infd, outfd;
+    int     save_errno;
+    ssize_t nb;
+    char    buf[32768];
+    char   *quoted;
+
+    if ((infd = open(src, O_RDONLY)) == -1) {
+       save_errno = errno;
+       quoted = quote_string(src);
+       *errmsg = vstralloc("Can't open file ", quoted, " for reading: %s",
+                           strerror(save_errno), NULL);
+       amfree(quoted);
+       return -1;
+    }
+
+    if ((outfd = open(dst, O_WRONLY|O_CREAT, 0600)) == -1) {
+       save_errno = errno;
+       quoted = quote_string(dst);
+       *errmsg = vstralloc("Can't open file ", quoted, " for writting: %s",
+                           strerror(save_errno), NULL);
+       amfree(quoted);
+       close(infd);
+       return -1;
+    }
+
+    while((nb=read(infd, &buf, SIZEOF(buf))) > 0) {
+       if(fullwrite(outfd,&buf,(size_t)nb) < nb) {
+           save_errno = errno;
+           quoted = quote_string(dst);
+           *errmsg = vstralloc("Error writing to \"", quoted, "\":",
+                               strerror(save_errno), NULL);
+           amfree(quoted);
+           close(infd);
+           close(outfd);
+           return -1;
+       }
+    }
+
+    if (nb < 0) {
+       save_errno = errno;
+       quoted = quote_string(src);
+       *errmsg = vstralloc("Error reading from \"", quoted, "\":",
+                           strerror(save_errno), NULL);
+       amfree(quoted);
+       close(infd);
+       close(outfd);
+       return -1;
+    }
+
+    close(infd);
+    close(outfd);
+    return 0;
+}
+#ifndef HAVE_LIBREADLINE
+/*
+ * simple readline() replacements
+ */
+
+char *
+readline(
+    const char *prompt)
+{
+    printf("%s", prompt);
+    fflush(stdout);
+    fflush(stderr);
+    return agets(stdin);
+}
+
+void 
+add_history(
+    const char *line)
+{
+    (void)line;        /* Quite unused parameter warning */
+}
+#endif