From: Urban Wallasch Date: Fri, 25 Oct 2019 15:36:56 +0000 (+0200) Subject: * Replaced existing net.[ch] with new versions. X-Git-Tag: v0.1.0~8 X-Git-Url: https://git.packet-gain.de/?a=commitdiff_plain;h=e3a18bfc378b0cf77902940a18cdabad974bb515;p=ets2_tele.git * Replaced existing net.[ch] with new versions. * Replaced read/write with timeout capable versions in telehttpd. * Dropped fserv.[ch], functionality covered by new net.c. --- diff --git a/Makefile b/Makefile index 02beae3..4ad0cf1 100644 --- 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 index c668dbf..0000000 --- a/fserv.c +++ /dev/null @@ -1,97 +0,0 @@ -#include -#include -#include - -#include -#include -#include -#include -#include - -#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 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 -#include - -/* 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 5b54db6..296155e 100644 --- a/net.c +++ b/net.c @@ -1,158 +1,458 @@ /* * 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 #include +#include #include - +#include #include -#include -#include -#include -#include #include +#include -#include +#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 c299bf9..a57f50b 100644 --- a/net.h +++ b/net.h @@ -1,16 +1,256 @@ /* * 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 + + +/* + * 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 - 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 - error code returned by any of the above functions + * buf - user supplied buffer to store the error message + * len - 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 - local node address (numerical or name); NULL for any + * svc - service (port number or service name) + * st - socket type; one of SOCK_STREAM or SOCK_DGRAM + * af - 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 - remote node address (numerical or host name) + * svc - remote service (port number or service name) + * addr - local node address to bind to, or NULL for none + * st - socket type; one of SOCK_STREAM or SOCK_DGRAM + * af - 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 - 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 - TCP listener socket + * timeout - 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 - file or socket descriptor to read from + * buf - user supplied buffer to store the received message + * len - maximum number of bytes to store in buf + * flags - additional flags to pass, see: man recv + * addr - see: man recvfrom + * alen - see: man recvfrom + * timeout - 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 - file or socket descriptor to read from + * buf - user supplied buffer to store the received message + * len - maximum number of bytes to store in buf + * flags - additional flags to pass, see: man send + * addr - see: man sendto + * alen - see: man sendto + * timeout - 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 - file or socket descriptor to write to + * in_fd - file or socket descriptor to read from + * count - maximum number of bytes to copy + * timeout - 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_*/ diff --git a/telehttpd.c b/telehttpd.c index 70a38f3..7be9f97 100644 --- a/telehttpd.c +++ b/telehttpd.c @@ -13,8 +13,8 @@ #include #include #include +#include -#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);