* Replaced existing net.[ch] with new versions.
authorUrban Wallasch <urban.wallasch@freenet.de>
Fri, 25 Oct 2019 15:36:56 +0000 (17:36 +0200)
committerUrban Wallasch <urban.wallasch@freenet.de>
Fri, 25 Oct 2019 15:36:56 +0000 (17:36 +0200)
* Replaced read/write with timeout capable versions in telehttpd.
* Dropped fserv.[ch], functionality covered by new net.c.

Makefile
fserv.c [deleted file]
fserv.h [deleted file]
net.c
net.h
telehttpd.c

index 02beae3600de782fecf3140fa40a23b67e2516d8..4ad0cf19d89943f4a84a19bfea501143f00c07b2 100644 (file)
--- a/Makefile
+++ b/Makefile
@@ -13,7 +13,7 @@ SDK_INCLUDES=\
   -Isdk/include/amtrucks/ \
   -Isdk/include/eurotrucks2
 
-CFLAGS=-Wall -Wextra -std=c99 -O2 -I. -pthread
+CFLAGS=-Wall -Wextra -std=gnu11 -O2 -I. -pthread
 CXXFLAGS=-Wall -O2 -I. -pthread
 LDFLAGS=-pthread -lrt
 
@@ -27,7 +27,7 @@ endif
 
 COMMON_HDR := telemetry.h
 PLUGIN_SRC := teleshmem.cpp shmget.c
-HTTPD_OBJ  := telehttpd.o shmget.o net.o fserv.o tele2json.o telehelper.o
+HTTPD_OBJ  := telehttpd.o shmget.o net.o tele2json.o telehelper.o
 LOGGER_OBJ := telelogger.o shmget.o tele2json.o telehelper.o
 
 .PHONY: all debug clean
diff --git a/fserv.c b/fserv.c
deleted file mode 100644 (file)
index c668dbf..0000000
--- a/fserv.c
+++ /dev/null
@@ -1,97 +0,0 @@
-#include <stdio.h>
-#include <string.h>
-#include <errno.h>
-
-#include <fcntl.h>
-#include <unistd.h>
-#include <sys/types.h>
-#include <sys/stat.h>
-#include <sys/socket.h>
-
-#include "fserv.h"
-#include "log.h"
-
-
-#define BUFSIZE 65536
-
-
-int fserv_open_server( const char *fn, struct file_info_t *fi )
-{
-       struct stat stbuf;
-
-       fi->fd = open( fn, O_RDONLY );
-       if ( fi->fd < 0 )
-       {
-               EPRINT( "open '%s': %s\n", fn, strerror( errno ) );
-               return -1;
-       }
-       if ( fstat( fi->fd, &stbuf ) < 0 )
-       {
-               close( fi->fd );
-               fi->fd = -1;
-               EPRINT( "fstat: %s\n", strerror( errno ) );
-               return -1;
-       }
-       strncpy( fi->name, fn, sizeof fi->name - 1 );
-       fi->name[sizeof fi->name - 1] = '\0';
-       fi->remn = fi->size = stbuf.st_size;
-       fi->coff = 0;
-       DPRINT( "all ok\n" );
-       return 0;
-}
-
-int fserv_sendfile( int outfd, struct file_info_t *fi )
-{
-       off_t sent = 0;
-       int err = -1;
-       char buf[BUFSIZE];
-       size_t bread, bwr;
-
-       DPRINT( "send file contents\n" );
-       if ( fi->coff != lseek( fi->fd, fi->coff, SEEK_SET ) )
-       {
-               EPRINT( "lseek: %s\n", strerror( errno ) );
-               goto DONE;
-       }
-       while ( sent != fi->remn )
-       {
-               bread = read( fi->fd, buf, sizeof buf );
-               if ( bread == (size_t)-1 )
-               {
-                       EPRINT( "read: %s\n", strerror( errno ) );
-                       goto DONE;
-               }
-               if ( bread == 0 )
-               {
-                       EPRINT( "read: unexpected end of file?\n" );
-                       goto DONE;
-               }
-               bwr = send( outfd, buf, bread, MSG_NOSIGNAL );
-               if ( bwr == (size_t)-1 )
-               {
-                       EPRINT( "send: %s\n", strerror( errno ) );
-                       goto DONE;
-               }
-               if ( bwr != bread )
-               {
-                       DPRINT( "send returned %ld (expected %ld)\n", bwr, bread );
-                       goto DONE;
-               }
-               sent += bwr;
-       }
-       err = 0;
-DONE:
-       fi->remn -= sent;
-       DPRINT( "Sent %lu, remaining %lu, resume at %lu\n",
-                       sent, fi->remn, fi->coff );
-       return err;
-}
-
-void fserv_close( struct file_info_t *fi )
-{
-       WHOAMI;
-       close( fi->fd );
-       fi->fd = -1;
-       fi->coff = fi->remn = fi->size = 0;
-}
-
diff --git a/fserv.h b/fserv.h
deleted file mode 100644 (file)
index 8b32afc..0000000
--- a/fserv.h
+++ /dev/null
@@ -1,34 +0,0 @@
-/*
- * fserv.h
- *
- *  Created on: Mar 8, 2013
- *      Author: volpol
- *  Modifications:
- *     Use PATH_MAX. [uw 2014-03-21]
- */
-
-#ifndef FSERV_H_
-#define FSERV_H_
-
-#include <sys/types.h>
-#include <linux/limits.h>
-
-/* Yes, we all know that PATH_MAX /isn't/, but at least it's better
- * than something like 256.
- */
-
-struct file_info_t {
-       int32_t fd;
-       char name[PATH_MAX+1];
-       off_t size;
-       off_t remn;
-       off_t coff;
-};
-
-int fserv_open_server( const char *fn, struct file_info_t *fi );
-int fserv_open_client( struct file_info_t *fi );
-int fserv_sendfile( int outfd, struct file_info_t *fi );
-int fserv_recvfile( int infd, struct file_info_t *fi );
-void fserv_close( struct file_info_t *fi );
-
-#endif /* FSERV_H_ */
diff --git a/net.c b/net.c
index 5b54db667b372e1ffd24870c6c7436b8e656a6a9..296155eaabd8da32bb40710ffaad79b0fd08272e 100644 (file)
--- a/net.c
+++ b/net.c
 /*
  * net.c
  *
- *  Created on: Mar 8, 2013
- *      Author: volpol
- *  Modifed:
- *             2013-05-31      support bind address, statics no more [uw]
+ * Copyright (c) 2019, Urban Wallasch
+ * BSD 3-Clause License, see LICENSE file for more details.
+ *
+ * Collection of functions to help create simple IPv4 or IPv6 based
+ * TCP or UDP servers and clients.  See net.h for further information.
+ *
+ * Build with -DNET_ERR to enable error logging to stderr.
+ * Build with -DNET_DBG to enable error and debug logging to stderr.
+ *
  */
 
-// Needed for inet_aton() and herror():
-#define _DEFAULT_SOURCE
-
+#include <errno.h>
 #include <stdio.h>
+#include <stdlib.h>
 #include <string.h>
-
+#include <unistd.h>
 #include <poll.h>
-#include <sys/types.h>
-#include <sys/socket.h>
-#include <arpa/inet.h>
-#include <netinet/in.h>
 #include <netdb.h>
+#include <arpa/inet.h>
 
-#include <unistd.h>
+#include "net.h"
+
+
+/* Size of copy buffer in sendfile_tm(): */
+#define SENDFILE_TM_BUFSIZE  64000
+
+/* Retry attempts for zero byte write in sendfile_tm(): */
+#define SENDFILE_TM_RETRY_0  5
+
+
+/* Logging support macros and helper functions: */
+
+#if defined(NET_DBG) && !defined(NET_ERR)
+#define NET_ERR
+#endif
+
+#ifdef NET_ERR
+
+/* Log helper function: create "host:port" string from socket address: */
+static char *addr2str(const struct sockaddr *addr, socklen_t addrlen,
+                      char *buf, size_t bufsize) {
+    char host[64], svc[10];
+    int err;
+    err = getnameinfo(addr, addrlen, host, sizeof host, svc, sizeof svc,
+                      NI_NUMERICHOST | NI_NUMERICSERV);
+    if (err == 0)
+        snprintf(buf, bufsize, "%s:%s", host, svc);
+    else
+        snprintf(buf, bufsize, "<?>:<?>");
+    return buf;
+}
+
+#define ADDR2STR(a_,l_,b_,s_)   char (b_)[(s_)]; addr2str((a_),(l_),(b_),(s_))
+
+#define NETLOG_MSG(fi_,fn_,ln_,...)     do {                           \
+            fprintf(stderr,"%s:%s@%d: ",fi_,fn_,ln_);                  \
+            fprintf(stderr,__VA_ARGS__);                               \
+        } while(0)
+
+#define NETLOG_ERR(e_,...)      do {                                   \
+            char ebuf[100];                                            \
+            NETLOG_MSG(__FILE__,__func__,__LINE__,__VA_ARGS__);        \
+            fprintf(stderr,": %s\n",net_strerror(e_,ebuf,sizeof ebuf));\
+        } while(0)
+
+#ifdef NET_DBG
+#define NETLOG_DBG(...)     do {                                       \
+            NETLOG_MSG(__FILE__,__func__,__LINE__,"DEBUG: "__VA_ARGS__);\
+            fprintf(stderr,"\n");                                      \
+        } while(0)
+#else
+#define NETLOG_DBG(...)
+#endif
+
+#else
+#define ADDR2STR(...)
+#define NETLOG_ERR(...)
+#define NETLOG_DBG(...)
+#endif  /* NET_ERR */
+
+
+/* Helper function to bind a client socket to local node address: */
+static int net_bind_local(int sock, const char *addr, int st, int af ) {
+    /* Only try to bind if actually requested! */
+    if (addr == NULL || *addr == '\0' || *addr == '*')
+        return 0;
+
+    int err;
+    struct addrinfo hints, *info;
+
+    memset(&hints, 0, sizeof hints);
+    hints.ai_family = af;
+    hints.ai_socktype = st;
+    hints.ai_flags = AI_V4MAPPED | AI_ADDRCONFIG;
+    err = getaddrinfo(addr, NULL, &hints, &info);
+    if (err != 0) {
+        NETLOG_ERR(err, "getaddrinfo(%s)", addr);
+        return err;
+    }
+    for (struct addrinfo *ai = info; ai != NULL; ai = ai->ai_next) {
+        ADDR2STR(ai->ai_addr, ai->ai_addrlen, stra, 100);
+        err = bind(sock, ai->ai_addr, ai->ai_addrlen);
+        if (err != 0) {
+            NETLOG_ERR(EAI_SYSTEM, "bind(%d, %s)", sock, stra);
+            continue;
+        }
+        else {
+            NETLOG_DBG("bind(%d, %s): Ok", sock, stra);
+        }
+        break;
+    }
+    freeaddrinfo(info);
+    return err < 0 ? EAI_SYSTEM : 0;
+}
+
+/* Check, if an error return code indicates system error: */
+int net_issyserr(int errnum) {
+    return (errnum == EAI_SYSTEM);
+}
+
+/* Copy textual description of last error to user supplied buffer: */
+char *net_strerror(int errnum, char *buf, size_t len) {
+    if (errnum >= 0)
+        snprintf(buf, len, "Success");
+    else if (net_issyserr(errnum))
+        strerror_r(errno, buf, len);
+    else
+        snprintf(buf, len, gai_strerror(errnum));
+    return buf;
+}
+
+/* Create and bind a new listener socket: */
+int net_open_server(const char *addr, const char *svc, int st, int af) {
+    int err, sock = -1;
+    struct addrinfo hints, *info;
+
+    addr = addr && *addr && *addr != '*' ? addr : NULL;
+    memset(&hints, 0, sizeof hints);
+    hints.ai_family = af;
+    hints.ai_socktype = st;
+    hints.ai_flags = AI_V4MAPPED | AI_ADDRCONFIG | AI_PASSIVE;
+    err = getaddrinfo(addr, svc, &hints, &info);
+    if (err != 0) {
+        NETLOG_ERR(err, "getaddrinfo(%s,%s)", addr, svc);
+        return err;
+    }
+    for (struct addrinfo *ai = info; ai != NULL; ai = ai->ai_next) {
+        int set = 1;
+        sock = socket(ai->ai_family, ai->ai_socktype, ai->ai_protocol);
+        if (sock < 0) {
+            NETLOG_ERR(EAI_SYSTEM, "socket()");
+            continue;
+        }
+        err = setsockopt(sock, SOL_SOCKET, SO_REUSEADDR, &set, sizeof set);
+        if (err != 0) {
+            NETLOG_ERR(EAI_SYSTEM, "setsockopt(%d)", sock);
+            close(sock);
+            sock = -1;
+            continue;
+        }
+        ADDR2STR(ai->ai_addr, ai->ai_addrlen, stra, 100);
+        err = bind(sock, ai->ai_addr, ai->ai_addrlen);
+        if (err != 0) {
+            NETLOG_ERR(EAI_SYSTEM, "bind(%d, %s)", sock, stra);
+            close(sock);
+            sock = -1;
+            continue;
+        }
+        else {
+            NETLOG_DBG("bind(%d, %s): Ok", sock, stra);
+        }
+        break;
+    }
+    freeaddrinfo(info);
+    if (sock < 0)
+        return EAI_SYSTEM;
+    if (st == SOCK_STREAM) {
+        err = listen(sock, 10);
+        if (err != 0) {
+            NETLOG_ERR(EAI_SYSTEM, "listen(%d)", sock);
+            close(sock);
+            return EAI_SYSTEM;
+        }
+    }
+    return sock;
+}
 
-#include "log.h"
-
-
-int net_open_client( const char *host, unsigned short port, const char *bind_addr )
-{
-       int sock = -1;
-       struct sockaddr_in server;
-       struct hostent *he = NULL;
-
-       //WHOAMI;
-       DPRINT( "%s:%d [%s]\n", host, port, bind_addr ? bind_addr : "*" );
-       if ( 0 > ( sock = socket( AF_INET, SOCK_STREAM, IPPROTO_TCP ) ) )
-               return perror( "socket()" ), -1;
-       server.sin_port = htons( port );
-       server.sin_family = AF_INET;
-       if ( NULL == ( he = gethostbyname( host ) ) )
-       {
-               herror( "gethostbyname()" );
-               goto ERROUT;
-       }
-       if ( NULL != bind_addr )
-       {
-               struct sockaddr_in lob;
-               lob.sin_family = AF_INET;
-               if ( 0 != strcmp( bind_addr, "*" ) )
-               {
-                       if ( 0 == strcmp( bind_addr, "localhost" ) )
-                               bind_addr = "127.0.0.1";
-                       lob.sin_family = AF_INET;
-                       if ( 0 == inet_aton( bind_addr, (struct in_addr *)&lob.sin_addr.s_addr ) )
-                       {
-                               fprintf( stderr, "inet_aton() failed\n" );
-                               goto ERROUT;
-                       }
-               }
-               else
-                       lob.sin_addr.s_addr = INADDR_ANY;
-               if ( 0 > bind( sock, (const struct sockaddr*)&lob, sizeof lob ) )
-               {
-                       perror( "bind()" );
-                       goto ERROUT;
-               }
-       }
-       server.sin_addr.s_addr = *((in_addr_t*)he->h_addr_list[0]);
-       if ( 0 != connect( sock, (struct sockaddr *)&server, sizeof server ) )
-       {
-               perror( "connect()" );
-               goto ERROUT;
-       }
-       return sock;
-ERROUT:
-       close( sock );
-       return -1;
+/* Create and connect a new socket: */
+int net_open_client(const char *host, const char *svc, const char *addr, int st, int af) {
+    int err, sock = -1;
+    struct addrinfo hints, *info;
+
+    host = host && *host ? host : NULL;
+    memset(&hints, 0, sizeof hints);
+    hints.ai_family = af;
+    hints.ai_socktype = st;
+    hints.ai_flags = AI_V4MAPPED | AI_ADDRCONFIG;
+    err = getaddrinfo(host, svc, &hints, &info);
+    if (err != 0) {
+        NETLOG_ERR(err, "getaddrinfo(%s,%s)", host, svc);
+        return err;
+    }
+    for (struct addrinfo *ai = info; ai != NULL; ai = ai->ai_next) {
+        int set = 1;
+        sock = socket(ai->ai_family, ai->ai_socktype, ai->ai_protocol);
+        if (sock < 0) {
+            NETLOG_ERR(EAI_SYSTEM, "socket()");
+            continue;
+        }
+        err = setsockopt(sock, SOL_SOCKET, SO_REUSEADDR, &set, sizeof set);
+        if (err != 0) {
+            NETLOG_ERR(EAI_SYSTEM, "setsockopt(%d)", sock);
+            close(sock);
+            sock = -1;
+            continue;
+        }
+        err = net_bind_local(sock, addr, ai->ai_socktype, ai->ai_family);
+        if (err != 0) {
+            /* Errors already logged in net_bind_local()! */
+            close(sock);
+            sock = -1;
+            continue;
+        }
+        ADDR2STR(ai->ai_addr, ai->ai_addrlen, stra, 100);
+        err = connect(sock, ai->ai_addr, ai->ai_addrlen);
+        if (err != 0) {
+            NETLOG_ERR(EAI_SYSTEM, "connect(%d, %s)", sock, stra);
+            close(sock);
+            sock = -1;
+            continue;
+        }
+        else {
+            NETLOG_DBG("connect(%d, %s): Ok", sock, stra);
+        }
+        break;
+    }
+    freeaddrinfo(info);
+    if (sock < 0)
+        return EAI_SYSTEM;
+    return sock;
 }
 
-int net_accept( int sock )
-{
-       WHOAMI;
-       int conn = -1;
-    struct pollfd pe;
-    int res;
-
-    pe.fd = sock;
-    pe.events = POLLIN;
-    pe.revents = 0;
-    if( (0 < (res = poll (&pe, 1, 1000))) && pe.revents == POLLIN){
-        if ( 0 > ( conn = accept( sock, NULL, NULL ) ) )
-            perror( "accept()" );
-        else
-        {
-            DPRINT( "accepted conn: %d\n", conn );
-        }
-    } else {
-        return -1;
-    }
-       return conn;
+/* Close socket: */
+int net_close(int sock) {
+    int err;
+    err = shutdown(sock, SHUT_RDWR);
+    if (err < 0) {
+        /* This error is not forwarded to caller! */
+        NETLOG_DBG("shutdown(%d): %s", sock, strerror(errno));
+    }
+    err = close(sock);
+    if (err < 0) {
+        NETLOG_ERR(EAI_SYSTEM, "close(%d)", sock);
+    }
+    return err < 0 ? EAI_SYSTEM : 0;
 }
 
-void net_close( int conn )
-{
-       WHOAMI;
-       if ( 0 <= conn )
-       {
-               DPRINT( "shutting down conn: %d\n", conn );
-               shutdown( conn, SHUT_RDWR );
-               close( conn );
-       }
+/* Wait for and accept a TCP connection request: */
+int tcp_accept(int sock, int timeout) {
+    int r;
+    struct pollfd pfd;
+
+    pfd.fd = sock;
+    pfd.events = POLLIN;
+    r = poll(&pfd, 1, timeout);
+    if (r == 1) {
+        if (pfd.revents & (POLLIN | POLLERR | POLLHUP)) {
+            /* In case of POLLERR or POLLHUP we let accept() catch the error! */
+            struct sockaddr_storage addr;
+            struct sockaddr *paddr = (struct sockaddr *)&addr;
+            socklen_t addrlen = sizeof addr;
+            r = accept(sock, paddr, &addrlen);
+            if (r < 0) {
+                r = EAI_SYSTEM;
+            }
+            else {
+                ADDR2STR(paddr, addrlen, stra, 100);
+                NETLOG_DBG("accept(%d) -> (%d, %s): Ok", sock, r, stra);
+            }
+        }
+        else if (pfd.revents & POLLNVAL) {
+            errno = EBADF;
+            r = EAI_SYSTEM;
+        }
+        else {
+            NETLOG_DBG("This should never happen! (r=%d, revents=0x%04X)",
+                        r, (unsigned)pfd.revents);
+            errno = EIO;
+            r = EAI_SYSTEM;
+        }
+    }
+    else if (r == 0) {
+        errno = ETIME;
+        r = EAI_SYSTEM;
+    }
+    else if (r < 0) {
+        r = EAI_SYSTEM;
+    }
+    if (r < 0) {
+        NETLOG_ERR(r, "tcp_accept(%d)", sock);
+    }
+    return r;
 }
 
-int net_open_server( unsigned short port, const char *bind_addr  )
-{
-       int sock;
-       int reuse = 1;
-       struct sockaddr_in lob;
-
-       //WHOAMI;
-       DPRINT( "%d [%s]\n", port, bind_addr ? bind_addr : "*" );
-       if ( 0 > ( sock = socket( AF_INET, SOCK_STREAM, IPPROTO_TCP ) ) )
-               return perror( "socket()" ), -1;
-       if ( 0 != setsockopt( sock, SOL_SOCKET, SO_REUSEADDR, &reuse, sizeof reuse ) )
-       {
-               perror( "setsockopt()" );
-               goto ERROUT;
-       }
-       lob.sin_family = AF_INET;
-       lob.sin_port = htons( port );
-       if ( NULL != bind_addr && 0 != strcmp( bind_addr, "*" ) )
-       {
-               if ( 0 == strcmp( bind_addr, "localhost" ) )
-                       bind_addr = "127.0.0.1";
-               lob.sin_family = AF_INET;
-               if ( 0 == inet_aton( bind_addr, (struct in_addr *)&lob.sin_addr.s_addr ) )
-               {
-                       fprintf( stderr, "inet_aton() failed\n" );
-                       goto ERROUT;
-               }
-       }
-       else
-               lob.sin_addr.s_addr = INADDR_ANY;
-       if ( 0 > bind( sock, (const struct sockaddr*)&lob, sizeof lob ) )
-       {
-               perror( "bind()" );
-               goto ERROUT;
-       }
-       if ( 0 > listen( sock, 0 ) )
-       {
-               perror( "listen()" );
-               goto ERROUT;
-       }
-       return sock;
-ERROUT:
-       close( sock );
-       return -1;
+/* Read from socket or file descriptor: */
+ssize_t recvfrom_tm(int fd, void *buf, size_t len, int flags,
+                    struct sockaddr *addr, socklen_t *alen, int timeout) {
+    ssize_t r;
+    struct pollfd pfd;
+
+    pfd.fd = fd;
+    pfd.events = POLLIN;
+    r = poll(&pfd, 1, timeout);
+    if (r == 1) {
+        if (pfd.revents & (POLLIN | POLLERR | POLLHUP)) {
+            /* In case of POLLERR or POLLHUP we let recv() catch the error! */
+            r = recvfrom(fd, buf, len, flags | MSG_DONTWAIT, addr, alen);
+            if (r < 0 && errno == ENOTSOCK) {
+                NETLOG_DBG("recvfrom_tm(%d): not a socket, trying read()", fd);
+                r = read(fd, buf, len);
+                if (r >= 0 && alen != NULL)
+                    *alen = 0;
+            }
+            r = r < 0 ? EAI_SYSTEM : r;
+        }
+        else if (pfd.revents & POLLNVAL) {
+            errno = EBADF;
+            r = EAI_SYSTEM;
+        }
+        else {
+            NETLOG_DBG("This should never happen! (r=%zd, revents=0x%04X)",
+                        r, (unsigned)pfd.revents);
+            errno = EIO;
+            r = EAI_SYSTEM;
+        }
+    }
+    else if (r == 0) {
+        errno = ETIME;
+        r = EAI_SYSTEM;
+    }
+    else if (r < 0) {
+        r = EAI_SYSTEM;
+    }
+    if (r < 0) {
+        NETLOG_ERR(r, "recvfrom_tm(%d)", fd);
+    }
+    else {
+        NETLOG_DBG("recvfrom_tm(%d): %zd bytes read", fd, r);
+    }
+    return r;
 }
+
+/* Write to socket or file descriptor: */
+ssize_t sendto_tm(int fd, const void *buf, size_t len, int flags,
+                  const struct sockaddr *addr, socklen_t alen, int timeout) {
+    ssize_t r;
+    struct pollfd pfd;
+
+    pfd.fd = fd;
+    pfd.events = POLLOUT;
+    r = poll(&pfd, 1, timeout);
+    if (r == 1) {
+        if (pfd.revents & (POLLOUT | POLLERR | POLLHUP)) {
+            /* In case of POLLERR or POLLHUP we let send() catch the error! */
+            r = sendto(fd, buf, len, flags | MSG_DONTWAIT | MSG_NOSIGNAL, addr, alen);
+            if (r < 0 && errno == ENOTSOCK) {
+                NETLOG_DBG("sendto_tm(%d): not a socket, trying write()", fd);
+                r = write(fd, buf, len);
+            }
+            r = r < 0 ? EAI_SYSTEM : r;
+        }
+        else if (pfd.revents & POLLNVAL) {
+            errno = EBADF;
+            r = EAI_SYSTEM;
+        }
+        else {
+            NETLOG_DBG("This should never happen! (r=%zd, revents=0x%04X)",
+                        r, (unsigned)pfd.revents);
+            errno = EIO;
+            r = EAI_SYSTEM;
+        }
+    }
+    else if (r == 0) {
+        errno = ETIME;
+        r = EAI_SYSTEM;
+    }
+    else if (r < 0) {
+        r = EAI_SYSTEM;
+    }
+    if (r < 0) {
+        NETLOG_ERR(r, "sendto_tm(%d)", fd);
+    }
+    else {
+        NETLOG_DBG("sendto_tm(%d): %zd bytes written", fd, r);
+    }
+    return r;
+}
+
+/* Copy count bytes from in_fd to out_fd: */
+int64_t sendfile_tm(int out_fd, int in_fd, int64_t count, int timeout) {
+    int64_t ret, total;
+    char *buf;
+
+    buf = malloc(SENDFILE_TM_BUFSIZE);
+    if (buf == NULL)
+        return EAI_SYSTEM;
+
+    for (total = 0, ret = 0; ret == 0; ) {
+        int retry_zero_wr;
+        ssize_t nrd, nwr, n;
+        n = SENDFILE_TM_BUFSIZE;
+        if (count >= 0 && n > count - total)
+            n = count - total;
+        nrd = read_tm(in_fd, buf, n, timeout);
+        if (nrd < 0) {
+            if (errno == EINTR || errno == EAGAIN || errno == EWOULDBLOCK)
+                continue;
+            ret = nrd;
+            continue;
+        }
+        else if (nrd == 0) {    /* all done */
+            ret = total;
+            break;  /* Avoid infinite loop on empty input! */
+        }
+        retry_zero_wr = SENDFILE_TM_RETRY_0;
+        nwr = 0;
+        while (nwr < nrd && ret == 0) {
+            n = write_tm(out_fd, buf + nwr, nrd - nwr, timeout);
+            if (n < 0) {
+                if (errno == EINTR || errno == EAGAIN || errno == EWOULDBLOCK)
+                    continue;
+                ret = n;
+                continue;
+            }
+            else if (n == 0) {
+                if (retry_zero_wr-- <= 0) {
+                    errno = ENOSPC;
+                    ret = EAI_SYSTEM;
+                    NETLOG_ERR(ret, "sendfile_tm(%d,%d)", out_fd, in_fd);
+                    continue;
+                }
+                NETLOG_DBG("sendfile_tm(%d,%d): zero write, retry %d",
+                        out_fd, in_fd, SENDFILE_TM_RETRY_0 - retry_zero_wr);
+            }
+            else {
+                retry_zero_wr = SENDFILE_TM_RETRY_0;
+            }
+            nwr += n;
+        }
+        total += nwr;
+    }
+    free(buf);
+    return ret;
+}
+
diff --git a/net.h b/net.h
index c299bf950ae77c9f0b9577c39cceabc55eaaa72c..a57f50bfc1e50d08e84fc0b9dadf4f357e0f9278 100644 (file)
--- a/net.h
+++ b/net.h
 /*
  * net.h
  *
- *  Created on: Mar 8, 2013
- *      Author: volpol
+ * Copyright (c) 2019, Urban Wallasch
+ * BSD 3-Clause License, see LICENSE file for more details.
+ *
+ * Collection of functions to help create simple IPv4 or IPv6 based
+ * TCP or UDP servers and clients.
+ *
+ * XXX_open_server - functions and macros to set up a simple server
+ *
+ * XXX_open_client - functions and macros to connect to a network host
+ *
+ * net_close       - close a network socket
+ *
+ * tcp_accept      - accept a TCP connection request on listener socket
+ *
+ * net_issyserr    - determine, if return code indicates system error
+ *
+ * net_strerror    - get textual error message
+ *
+ * recvfrom_tm
+ * recv_tm
+ * read_tm         - functions and macros to read with timeout
+ *
+ * sendto_tm
+ * send_tm
+ * write_tm        - functions and macros to write with timeout
+ *
+ * sendfile_tm     - copy data from one file descriptor to another
+ *
  */
 
 #ifndef NET_H_
 #define NET_H_
 
-int net_accept( int sock );
-int net_open_server( unsigned short port, const char *bind_addr );
-int net_open_client( const char *host, unsigned short port, const char *bind_addr );
-void net_close( int conn );
+#ifdef cplusplus
+extern "C" {
+#endif
+
+#include <sys/socket.h>
+
+
+/*
+ * Test if an error return code indicates a system level error.
+ *
+ * Returns either 1, in which case the actual error code can be found
+ * in errno, or 0, which means errnum should be checked against the
+ * list of EAI_XXX constants defined in netdb.h.
+ *
+ * errnum - <int> a negative value returned by any function from net.h
+ *
+ * Note: So far the only functions which may return non-system errors
+ * are those of the XXX_open_server() and XXX_open_client() varieties.
+ * In other words, it is safe to assume that all other functions
+ * declared in net.h in case of failure store their error codes in
+ * errno.
+ *
+ * See also: net_strerror.
+ */
+extern int net_issyserr(int errnum);
+
+
+/*
+ * Copy textual description of last error to user supplied buffer.
+ *
+ * Returns a pointer to the buffer.
+ *
+ * errnum - <int> error code returned by any of the above functions
+ * buf    - <char*> user supplied buffer to store the error message
+ * len    - <size_t> maximum number of characters buf can hold
+ *
+ * Note: Depending on the actual value of errnum this function will
+ * return a message based on either strerror_r() or gai_strerror().
+ */
+extern char *net_strerror(int errnum, char *buf, size_t len);
+
+
+/*
+ * Create a new socket with the specified characteristics, bind it to
+ * the local node address and, in case of SOCK_STREAM, start listening
+ * for connection requests which can be accepted using tcp_accept().
+ *
+ * Returns the bound listener socket, or a negative value on error.
+ *
+ * addr - <string> local node address (numerical or name); NULL for any
+ * svc  - <string> service (port number or service name)
+ * st   - <int> socket type; one of SOCK_STREAM or SOCK_DGRAM
+ * af   - <int> address family; one of AF_UNSPEC, AF_INET, AF_INET6
+ *
+ * Note: Address family AF_INET6 will allow mapped IPv4 connections.
+ * AF_UNSPEC will cause the first suitable bind address to be used,
+ * the sort order is determined by the host configuration, e.g. in
+ * /etc/gai.conf for glibc.
+ */
+extern int net_open_server(const char *addr, const char *svc, int st, int af);
+
+/*
+ * Shorthand macros for socket type and address family permutations:
+ */
+#define tcp_open_server(a_,s_)   net_open_server((a_),(s_),SOCK_STREAM,AF_UNSPEC)
+#define tcp4_open_server(a_,s_)  net_open_server((a_),(s_),SOCK_STREAM,AF_INET)
+#define tcp6_open_server(a_,s_)  net_open_server((a_),(s_),SOCK_STREAM,AF_INET6)
+#define udp_open_server(a_,s_)   net_open_server((a_),(s_),SOCK_DGRAM,AF_UNSPEC)
+#define udp4_open_server(a_,s_)  net_open_server((a_),(s_),SOCK_DGRAM,AF_INET)
+#define udp6_open_server(a_,s_)  net_open_server((a_),(s_),SOCK_DGRAM,AF_INET6)
+
+
+/*
+ * Create a new socket, optionally bind it to a local node address,
+ * associate it with a random ephemeral port and connect it to the
+ * specified host and service.
+ *
+ * Returns the connected socket, or a negative value on error.
+ *
+ * host - <string> remote node address (numerical or host name)
+ * svc  - <string> remote service (port number or service name)
+ * addr - <string> local node address to bind to, or NULL for none
+ * st   - <int> socket type; one of SOCK_STREAM or SOCK_DGRAM
+ * af   - <int> address family; one of AF_UNSPEC, AF_INET, AF_INET6
+ */
+extern int net_open_client(const char *host, const char *svc, const char *addr, int st, int af);
+
+/*
+ * Shorthand macros for socket type and address family permutations:
+ */
+#define tcp_open_client(h_,s_,a_)   net_open_client((h_),(s_),(a_),SOCK_STREAM,AF_UNSPEC)
+#define tcp4_open_client(h_,s_,a_)  net_open_client((h_),(s_),(a_),SOCK_STREAM,AF_INET)
+#define tcp6_open_client(h_,s_,a_)  net_open_client((h_),(s_),(a_),SOCK_STREAM,AF_INET6)
+#define udp_open_client(h_,s_,a_)   net_open_client((h_),(s_),(a_),SOCK_DGRAM,AF_UNSPEC)
+#define udp4_open_client(h_,s_,a_)  net_open_client((h_),(s_),(a_),SOCK_DGRAM,AF_INET)
+#define udp6_open_client(h_,s_,a_)  net_open_client((h_),(s_),(a_),SOCK_DGRAM,AF_INET6)
+
+
+/*
+ * Shutdown and close the specified socket.
+ *
+ * Returns 0 on success, or a negative value on error.
+ *
+ * Note: Errors returned by shutdown() are silently ignored and not
+ * forwarded.
+ *
+ * sock - <int> socket to close
+ */
+extern int net_close(int sock);
+
+
+/*
+ * Wait at most timeout milliseconds for a TCP connection request on
+ * a listener socket to arrive and accept any such request.
+ *
+ * Returns a newly created socket associated with the accepted TCP
+ * connection, or a negative value on error.
+ *
+ * sock    - <int> TCP listener socket
+ * timeout - <int> timeout in milliseconds
+ *
+ * Note: When the time-out expires a negative value is returned and
+ * errno is set to ETIME.  A negative value for timeout will cause
+ * tcp_accept() to wait indefinitely.  Set timeout to zero for immediate
+ * return.  The function will return prematurely when interrupted by a
+ * signal.
+ */
+extern int tcp_accept(int sock, int timeout);
+
+
+/*
+ * Read from file or socket descriptor with timeout.
+ *
+ * Returns the number of bytes received, or a negative value on error.
+ *
+ * fd      - <int> file or socket descriptor to read from
+ * buf     - <char*> user supplied buffer to store the received message
+ * len     -  <size_t> maximum number of bytes to store in buf
+ * flags   - <int> additional flags to pass, see: man recv
+ * addr    - <struct sockaddr*> see: man recvfrom
+ * alen    - <socklen_t*> see: man recvfrom
+ * timeout - <int> timeout in milliseconds
+ *
+ * Note: A zero return value indicates the equivalent of an EOF condition.
+ * When reading from a socket, the MSG_DONTWAIT flag is added in the
+ * underlying call to recvfrom().  When the time-out expires, a negative
+ * value is returned and errno is set to ETIME.  A negative value for
+ * timeout will cause indefinite wait.  Set timeout to zero for immediate
+ * return.  The function will return prematurely when interrupted by a
+ * signal.  When reading from a non-socket file descriptor flags, addr
+ * and alen are ignored, and performance may be slightly inferior
+ * compared to a plain read() call.
+ */
+extern ssize_t recvfrom_tm(int fd, void *buf, size_t len, int flags,
+               struct sockaddr *addr, socklen_t *alen, int timeout);
+/*
+ * Shorthand macros to closely match recv() and read() prototypes:
+ */
+#define recv_tm(d_,b_,l_,f_,t_) recvfrom_tm((d_),(b_),(l_),(f_),NULL,NULL,(t_))
+#define read_tm(d_,b_,l_,t_)    recvfrom_tm((d_),(b_),(l_),0,NULL,NULL,(t_))
+
+
+/*
+ * Write to file or socket descriptor with timeout.
+ *
+ * Returns the number of bytes sent, or a negative value on error.
+ *
+ * fd      - <int> file or socket descriptor to read from
+ * buf     - <char*> user supplied buffer to store the received message
+ * len     - <size_t> maximum number of bytes to store in buf
+ * flags   - <int> additional flags to pass, see: man send
+ * addr    - <struct sockaddr*> see: man sendto
+ * alen    - <socklen_t*> see: man sendto
+ * timeout - <int> timeout in milliseconds
+ *
+ * Note: A zero return value indicates the equivalent of an EOF condition.
+ * When writing to a socket, the MSG_DONTWAIT and MSG_NOSIGNAL flags are
+ * added in the underlying call to sendto().  When the time-out expires,
+ * a negative value is returned and errno is set to ETIME.  A negative
+ * value for timeout will cause indefinite wait.  Set timeout to zero
+ * for immediate return.  The function will return prematurely when
+ * interrupted by a signal.  When writing to a non-socket file descriptor
+ * flags, addr and alen are ignored, and performance may be slightly
+ * inferior compared to a plain write() call.
+ */
+extern ssize_t sendto_tm(int fd, const void *buf, size_t len, int flags,
+               const struct sockaddr *addr, socklen_t alen, int timeout);
+/*
+ * Shorthand macros to closely match send() and write() prototypes:
+ */
+#define send_tm(d_,b_,l_,f_,t_) sendto_tm((d_),(b_),(l_),(f_),NULL,0,(t_))
+#define write_tm(d_,b_,l_,t_)   sendto_tm((d_),(b_),(l_),0,NULL,0,(t_))
+
+
+/*
+ * Copy up to count bytes of data from in_fd to out_fd with timeout.
+ *
+ * Returns the total number of bytes copied, or a negative value on error.
+ *
+ * out_fd  - <int> file or socket descriptor to write to
+ * in_fd   - <int> file or socket descriptor to read from
+ * count   - <int64_t> maximum number of bytes to copy
+ * timeout - <int> timeout in milliseconds
+ *
+ * Note: The timeout is applied to each individual internal read or write
+ * operation.  If zero bytes were written, up to 5 attempts to retransmit
+ * are made.  Thus the timeout may add up multiple times.
+ *
+ * If count is negative, copying takes place until the end of the input
+ * stream is hit.  If count is zero a single read operation with a count
+ * of zero is performed.
+ */
+extern int64_t sendfile_tm(int out_fd, int in_fd, int64_t count, int timeout);
+
+
+#ifdef cplusplus
+}
+#endif
 
-#endif /* NET_H_ */
+#endif /*ndef NET_H_*/
index 70a38f3f130388d0e6d21a40759912c14bb7b60f..7be9f971f7dcf32ca049e6f183c4dc3a1517e0e2 100644 (file)
@@ -13,8 +13,8 @@
 #include <poll.h>
 #include <pthread.h>
 #include <unistd.h>
+#include <fcntl.h>
 
-#include "fserv.h"
 #include "net.h"
 #include "log.h"
 #include "shmget.h"
@@ -28,14 +28,14 @@ struct thread_data_t {
 };
 
 struct {
-    int port;
+    char *port;
     char *iface;
     char *host;
     char *origin;
     char *idxfile;
     bool retry;
 } cfg = {
-    8837,
+    "8837",
     "*",
     "localhost",
     "http://localhost",
@@ -54,9 +54,6 @@ static void handle_signal(int sig) {
     case SIGINT:
         force_quit = 1;
         break;
-    case SIGPIPE:
-        // We don't want to get killed by writing to a stale socket!
-        break;
     default:
         break;
     }
@@ -125,12 +122,12 @@ static size_t copy_field_val( char *buf, size_t bufsz, const char *needle, const
 
 static inline ssize_t sockwrite( int fd, const void *buf, size_t len ) {
     ssize_t n;
-    n = write( fd, buf, len );
+    n = write_tm( fd, buf, len, 1000 );
     if ( 0 > n ) {
-        EPRINT( "write: %s\n", strerror(errno) );
+        EPRINT( "write_tm: %s\n", strerror(errno) );
     }
     else if ( len != (size_t)n ) {
-        EPRINT( "write: short write (%d of %d)\n", (int)n, (int)len );
+        EPRINT( "write_tm: short write (%d of %d)\n", (int)n, (int)len );
     }
     return n;
 }
@@ -172,8 +169,9 @@ static void respond( struct thread_data_t *td, const char *req ) {
             sockwrite( td->sock, buf, tele2json( buf, sizeof buf, &td->tele ) );
         break;
     case r_index: {
-        struct file_info_t fi;
-        if ( 0 == fserv_open_server( cfg.idxfile, &fi ) ){
+        int fi;
+        fi = open( cfg.idxfile, O_RDONLY );
+        if ( 0 <= fi ){
             n = snprintf( buf, sizeof buf,
                     "HTTP/1.1 200 OK\r\n"
                     "Host: %s\r\n"
@@ -183,9 +181,9 @@ static void respond( struct thread_data_t *td, const char *req ) {
                     host
                 );
             if ( 0 < n && (size_t)n < sizeof buf && n == sockwrite( td->sock, buf, n ) ) {
-                fserv_sendfile( td->sock, &fi );
-                fserv_close( &fi );
+                sendfile_tm( td->sock, fi, -1, 5000 );
             }
+            close( fi );
         }
         else
             goto SEND_404;
@@ -234,7 +232,7 @@ static int rcv_request(int sock, char *buf, size_t size, int timeout) {
         res = poll(&pe, 1, timeout);
         if ( 0 < res && pe.revents == POLLIN ) {
             errno = 0;
-            bread = read(sock, buf + total, size - total);
+            bread = read_tm(sock, buf + total, size - total, 1000);
             if ( 0 > bread ) {
                 switch (errno) {
                 case EAGAIN:
@@ -282,14 +280,16 @@ static int serve_http(void) {
     pthread_t tid;
     pthread_attr_t attr;
 
-    ss = net_open_server( cfg.port, cfg.iface );
+    ss = tcp_open_server( cfg.iface, cfg.port );
     if ( ss < 0 ) {
-        EPRINT( "net_open_server failed!\n" );
+        char ebuf[200];
+        net_strerror( ss, ebuf, sizeof ebuf );
+        EPRINT( "tcp_open_server(): %s\n", ebuf );
         return -1;
     }
 
     while ( !force_quit ) {
-        as = net_accept( ss );
+        as = tcp_accept( ss, 1000 );
         if ( 0 != check_shm( false ) ) {
             if ( !cfg.retry )
                 return -1;
@@ -355,7 +355,7 @@ static int config( int argc, char *argv[] ) {
             cfg.origin = optarg;
             break;
         case 'p':
-            cfg.port = strtol(optarg, NULL, 10);
+            cfg.port = optarg;
             break;
         case 'r':
             cfg.retry = true;
@@ -379,7 +379,6 @@ static int config( int argc, char *argv[] ) {
 int main(int argc, char *argv[]) {
     signal(SIGTERM, handle_signal);
     signal(SIGINT,  handle_signal);
-    signal(SIGPIPE, handle_signal);
 
     if ( 0 != config( argc, argv ) )
         exit( EXIT_FAILURE);