diff --git a/.clang-format-ignore b/.clang-format-ignore index 53dcd68d7..4ed9bc6c3 100644 --- a/.clang-format-ignore +++ b/.clang-format-ignore @@ -5,6 +5,7 @@ subprojects/** src/common/eventmeth.cc include/pistache/eventmeth.h +include/pistache/emosandlibevdefs.h src/common/pist_timelog.cc include/pistache/pist_timelog.h src/common/pist_check.cc diff --git a/Building on BSD - FreeBSD, OpenBSD and NetBSD.txt b/Building on BSD - FreeBSD, OpenBSD and NetBSD.txt new file mode 100644 index 000000000..f6643b122 --- /dev/null +++ b/Building on BSD - FreeBSD, OpenBSD and NetBSD.txt @@ -0,0 +1,127 @@ +# SPDX-FileCopyrightText: 2024 Duncan Greatwood +# +# SPDX-License-Identifier: Apache-2.0 + +Making Pistache on *BSD +======================= + +Pistache has been built and tested on FreeBSD, OpenBSD and NetBSD. As +of July-2024, the versions used for testing were FreeBSD 13.3, +OpenBSD 7.3 and NetBSD 10.0. + +You will need to configure BSD with a working compiler. + +We would recommend that you also have python installed, including pip +(use "python -m ensure-pip" if needed, and add the directory where pip +is installed to your path). Plus, we recommend installing sudo, if not +preinstalled. + +You will need the following Pistache-dependencies installed: + git (and configure as needed) + llvm + meson + doxygen + googletest (*) + openssl + rapidjson (*) + howard-hinnant-date (*) + libevent + See BSD-type-specific notes below regarding installing these + dependencies. + +Convenience shell scripts are provided to make the build. Once +dependencies are installed, at the terminal, to build do: + bldscripts/mesbuild.sh +To test: + bldscripts/mestest.sh +To install: + bldscripts/mesinstall.sh + + +FreeBSD +======= + +Typically, required packages are installed using: + sudo pkg install +For instance: + sudo pkg install meson +Do this for each Pistache dependency, excluding howard-hinnant-date. + + +OpenBSD +======= + +Typically, required packages are installed using: + doas pkg_add +For instance: + doas pkg_add meson +Do this for each Pistache dependency, excluding googletest and +rapidjson. +(Note: You may use sudo instead of doas if you have installed the sudo +package and configured sudo; however, doas is often preferred on +OpenBSD.) + + +NetBSD +====== +Typically, required packages are installed using: + sudo pkg_in install +For instance: + sudo pkg_in install meson +Do this for each Pistache dependency, excluding howard-hinnant-date. + +Regarding NetBSD 9.4. NetBSD 9.4 uses gcc 7.5.0, while Pistache's +build files require C++17 support, and Pistache's code uses +std::filesystem. However, gcc 7.5.0 does not work correctly with +std::filesystem when C++17 is specified. Accordingly, we have tested +with NetBSD 10.0, not 9.4. Nonetheless, it is possible that Pistache +could be made to work on NetBSD 9.* with a different compiler or +different compiler version. + +Regarding the test net_test.invalid_address, it may be slow to execute +(about 2 minutes) in NetBSD. The cause is a long time out for the +system function getaddrinfo; it doesn't appear to an issue in +Pistache. + + +(*) Googletest, Rapidjson and Howard-hinnant-date Packages +========================================================== + +These packages are provided as Pistache subprojects, and so do not +have to be installed seperately on the BSD system. Also, note that +there is no howard-hinnant-date package supplied as part of the OS by +FreeBSD 13 nor by NetBSD 10; and no googletest or rapidjson packages +supplied by OpenBSD 7. Nonetheless, if you would like to install +googletest, rapidjson and/or howard-hinnant-date manually on the BSD +system, please proceed as follows: + +Googletest: + git clone https://github.com/google/googletest.git + cd googletest + mkdir build + cd build + cmake -G "Unix Makefiles" -DCMAKE_BUILD_TYPE=Release .. + make + sudo make install + +Rapidjson: + git clone https://github.com/Tencent/rapidjson/ + cd rapidjson/ + mkdir build + cd build + cmake -G "Unix Makefiles" -DCMAKE_BUILD_TYPE=Release .. + make + sudo make install + +Howard-hinnant-date: + git clone https://github.com/HowardHinnant/date.git + sudo mkdir /usr/local/include/date + sudo cp -p date/include/date/date.h /usr/local/include/date/. + +(Note: Typically, use doas instead of sudo on OpenBSD). + + +How It Works +============ +Pistache on BSD works very much as it does on macOS, i.e. by using the +libevent library to provide the core event loop. diff --git a/bldscripts/mesdebugflibevsetdirvars.sh b/bldscripts/mesdebugflibevsetdirvars.sh index d9088a38b..4614078c6 100644 --- a/bldscripts/mesdebugflibevsetdirvars.sh +++ b/bldscripts/mesdebugflibevsetdirvars.sh @@ -15,16 +15,15 @@ if [ "$(uname)" == "Darwin" ]; then exit 1 fi -MY_ARCH_NM=x86 -if [ "$(uname -m)" == "arm64" ]; then - MY_ARCH_NM=a64 -else - if [ "$(uname -m)" == "aarch64" ]; then - MY_ARCH_NM=a64 - fi +if [ "$(uname)" == "OpenBSD" ]; then + echo "Error: Don't force libevent on OpenBSD, libevent is on by default" + exit 1 fi - -MESON_BUILD_DIR=build${MY_ARCH_NM}.mes.flibev.debug -MESON_PREFIX_DIR=/usr/local +if [ "$(uname)" == "NetBSD" ]; then + echo "Error: Don't force libevent on NetBSD, libevent is on by default" + exit 1 +fi +PST_DIR_SUFFIX=".flibev.debug" +source bldscripts/messetdirvarsfinish.sh diff --git a/bldscripts/mesdebugsetdirvars.sh b/bldscripts/mesdebugsetdirvars.sh index cd8ddd412..06fab6f4f 100644 --- a/bldscripts/mesdebugsetdirvars.sh +++ b/bldscripts/mesdebugsetdirvars.sh @@ -10,22 +10,6 @@ # Use by: # source bldscripts/mesdebugsetdirvars.sh - -MY_ARCH_NM=x86 -if [ "$(uname -m)" == "arm64" ]; then - MY_ARCH_NM=a64 -else - if [ "$(uname -m)" == "aarch64" ]; then - MY_ARCH_NM=a64 - fi -fi - - -if [ "$(uname)" == "Darwin" ]; then - MESON_BUILD_DIR=build${MY_ARCH_NM}.mes.mac.debug - MESON_PREFIX_DIR=/usr/local -else - MESON_BUILD_DIR=build${MY_ARCH_NM}.mes.debug - MESON_PREFIX_DIR=/usr/local -fi +PST_DIR_SUFFIX=".debug" +source bldscripts/messetdirvarsfinish.sh diff --git a/bldscripts/mesflibevsetdirvars.sh b/bldscripts/mesflibevsetdirvars.sh index 67acb959a..bead6a7ce 100644 --- a/bldscripts/mesflibevsetdirvars.sh +++ b/bldscripts/mesflibevsetdirvars.sh @@ -15,16 +15,15 @@ if [ "$(uname)" == "Darwin" ]; then exit 1 fi -MY_ARCH_NM=x86 -if [ "$(uname -m)" == "arm64" ]; then - MY_ARCH_NM=a64 -else - if [ "$(uname -m)" == "aarch64" ]; then - MY_ARCH_NM=a64 - fi +if [ "$(uname)" == "OpenBSD" ]; then + echo "Error: Don't force libevent on OpenBSD, libevent is on by default" + exit 1 fi - -MESON_BUILD_DIR=build${MY_ARCH_NM}.mes.flibev -MESON_PREFIX_DIR=/usr/local +if [ "$(uname)" == "NetBSD" ]; then + echo "Error: Don't force libevent on NetBSD, libevent is on by default" + exit 1 +fi +PST_DIR_SUFFIX=".flibev" +source bldscripts/messetdirvarsfinish.sh diff --git a/bldscripts/messetdirvars.sh b/bldscripts/messetdirvars.sh index d5e15fcdb..2a835cc5b 100644 --- a/bldscripts/messetdirvars.sh +++ b/bldscripts/messetdirvars.sh @@ -10,22 +10,5 @@ # Use by: # source bldscripts/messetdirvars.sh - -MY_ARCH_NM=x86 -if [ "$(uname -m)" == "arm64" ]; then - MY_ARCH_NM=a64 -else - if [ "$(uname -m)" == "aarch64" ]; then - MY_ARCH_NM=a64 - fi -fi - - -if [ "$(uname)" == "Darwin" ]; then - MESON_BUILD_DIR=build${MY_ARCH_NM}.mes.mac - MESON_PREFIX_DIR=/usr/local -else - MESON_BUILD_DIR=build${MY_ARCH_NM}.mes - MESON_PREFIX_DIR=/usr/local -fi - +PST_DIR_SUFFIX= +source bldscripts/messetdirvarsfinish.sh diff --git a/bldscripts/messetdirvarsfinish.sh b/bldscripts/messetdirvarsfinish.sh new file mode 100644 index 000000000..3b4ff89f4 --- /dev/null +++ b/bldscripts/messetdirvarsfinish.sh @@ -0,0 +1,48 @@ +#!/bin/bash + +# +# SPDX-FileCopyrightText: 2024 Duncan Greatwood +# +# SPDX-License-Identifier: Apache-2.0 +# +# Sets MESON_BUILD_DIR and MESON_PREFIX_DIR +# +# NOT to be invoked directly by user. Ivoked by other set scripts once +# ... has been set +# +# Use by: +# source bldscripts/messetdirvarsfinish.sh + +MY_ARCH_NM=x86 +if [ "$(uname -m)" == "arm64" ]; then + MY_ARCH_NM=a64 +else + if [ "$(uname -m)" == "aarch64" ]; then + MY_ARCH_NM=a64 + fi +fi + + +if [ "$(uname)" == "Darwin" ]; then + MESON_BUILD_DIR=build${MY_ARCH_NM}.mes.mac${PST_DIR_SUFFIX} + MESON_PREFIX_DIR=/usr/local +else + if [[ "$OSTYPE" == "freebsd"* ]]; then + MESON_BUILD_DIR=build${MY_ARCH_NM}.mes.fbd${PST_DIR_SUFFIX} + MESON_PREFIX_DIR=/usr/local + else + if [[ "$OSTYPE" == "openbsd"* ]]; then + MESON_BUILD_DIR=build${MY_ARCH_NM}.mes.obd${PST_DIR_SUFFIX} + MESON_PREFIX_DIR=/usr/local + else + if [[ "$OSTYPE" == "netbsd"* ]]; then + MESON_BUILD_DIR=build${MY_ARCH_NM}.mes.nbd${PST_DIR_SUFFIX} + MESON_PREFIX_DIR=/usr/local + else + MESON_BUILD_DIR=build${MY_ARCH_NM}.mes${PST_DIR_SUFFIX} + MESON_PREFIX_DIR=/usr/local + fi + fi + fi +fi + diff --git a/include/pistache/emosandlibevdefs.h b/include/pistache/emosandlibevdefs.h new file mode 100644 index 000000000..be5f2ce5a --- /dev/null +++ b/include/pistache/emosandlibevdefs.h @@ -0,0 +1,76 @@ +/* + * SPDX-FileCopyrightText: 2024 Duncan Greatwood + * + * SPDX-License-Identifier: Apache-2.0 + */ + +// EventMeth Operating System and libevent defines + +// Defines, or does not define, _USE_LIBEVENT, _USE_LIBEVENT_LIKE_APPLE and +// _IS_BSD +// +// emosandlibevdefs.h + +#ifndef _EMOSANDLIBEVDEFS_H_ +#define _EMOSANDLIBEVDEFS_H_ + +/* ------------------------------------------------------------------------- */ + +#ifdef PISTACHE_FORCE_LIBEVENT + +// Force libevent even for Linux +#define _USE_LIBEVENT 1 + +// _USE_LIBEVENT_LIKE_APPLE not only forces libevent, but even in Linux causes +// the code to be as similar as possible to the way it is for __APPLE__ +// (e.g. wherever possible, even on Linux it uses solely OS calls that are +// also available on macOS) +// +// Can comment out if not wanted +#define _USE_LIBEVENT_LIKE_APPLE 1 + +#endif // ifdef PISTACHE_FORCE_LIBEVENT + +#ifdef _USE_LIBEVENT_LIKE_APPLE + #ifndef _USE_LIBEVENT + #define _USE_LIBEVENT 1 + #endif +#endif + +#ifdef __APPLE__ + #ifndef _USE_LIBEVENT + #define _USE_LIBEVENT 1 + #endif + #ifndef _USE_LIBEVENT_LIKE_APPLE + #define _USE_LIBEVENT_LIKE_APPLE 1 + #endif +#elif defined(_WIN32) // Defined for both 32-bit and 64-bit environments + #define _USE_LIBEVENT 1 +#elif defined(__unix__) || !defined(__APPLE__) && defined(__MACH__) + #include + #if defined(BSD) + // FreeBSD, NetBSD, OpenBSD, DragonFly BSD + // + // Note - FreeBSD may support epoll via FreeBSD's Linux emulation layer + // (see https://wiki.freebsd.org/Linuxulator). We can check for FreeBSD + // using "ifdef __FreeBSD__" and also check __FreeBSD_version (see + // https://docs.freebsd.org/en/books/porters-handbook/versions/). + // However, since FreeBSD's Linuxulator supports Linux binaries, a user + // might perhaps just as well run the Linux version of Pistache if + // using the Linuxulator. So we use libevent (which will likely use + // kqueue on FreeBSD) even in the FreeBSD case. + #ifndef _USE_LIBEVENT + #define _USE_LIBEVENT 1 + #endif + #ifndef _USE_LIBEVENT_LIKE_APPLE + #define _USE_LIBEVENT_LIKE_APPLE 1 + #endif + #ifndef _IS_BSD + #define _IS_BSD 1 + #endif + #endif +#endif + +/* ------------------------------------------------------------------------- */ + +#endif // ifndef _EMOSANDLIBEVDEFS_H_ diff --git a/include/pistache/eventmeth.h b/include/pistache/eventmeth.h index d7440a2be..604edba82 100644 --- a/include/pistache/eventmeth.h +++ b/include/pistache/eventmeth.h @@ -12,37 +12,7 @@ /* ------------------------------------------------------------------------- */ -#ifdef PISTACHE_FORCE_LIBEVENT - -// Force libevent even for Linux -#define _USE_LIBEVENT 1 - -// _USE_LIBEVENT_LIKE_APPLE not only forces libevent, but even in Linux causes -// the code to be as similar as possible to the way it is for __APPLE__ -// (e.g. wherever possible, even on Linux it uses solely OS calls that are -// also available on macOS) -// -// Can comment out if not wanted -#define _USE_LIBEVENT_LIKE_APPLE 1 - -#endif // ifdef PISTACHE_FORCE_LIBEVENT - -#ifdef _USE_LIBEVENT_LIKE_APPLE - #ifndef _USE_LIBEVENT - #define _USE_LIBEVENT 1 - #endif -#endif - -#ifdef __APPLE__ - #ifndef _USE_LIBEVENT - #define _USE_LIBEVENT 1 - #endif - #ifndef _USE_LIBEVENT_LIKE_APPLE - #define _USE_LIBEVENT_LIKE_APPLE 1 - #endif -#elif defined(_WIN32) // Defined for both 32-bit and 64-bit environments - #define _USE_LIBEVENT 1 -#endif +#include // Note: eventmeth wraps and abstracts capabilities of the libevent library, // providing interfaces to pistache that are similar to the epoll, eventfd and @@ -203,8 +173,6 @@ namespace Pistache #include #include -#include - #include #include #include @@ -427,4 +395,4 @@ namespace Pistache /* ------------------------------------------------------------------------- */ -#endif // ifdef _USE_LIBEVENT +#endif // ifndef _EVENTMETH_H_ diff --git a/include/pistache/meson.build b/include/pistache/meson.build index deacccf80..7331070e5 100644 --- a/include/pistache/meson.build +++ b/include/pistache/meson.build @@ -12,8 +12,9 @@ install_headers( 'config.h', 'cookie.h', 'description.h', + 'emosandlibevdefs.h', 'endpoint.h', - 'eventmeth.h', + 'eventmeth.h', 'errors.h', 'flags.h', 'http_defs.h', @@ -30,10 +31,10 @@ install_headers( 'os.h', 'peer.h', 'pist_quote.h', - 'pist_check.h', - 'pist_syslog.h', + 'pist_check.h', + 'pist_syslog.h', 'prototype.h', - 'pist_timelog.h', + 'pist_timelog.h', 'reactor.h', 'route_bind.h', 'router.h', diff --git a/include/pistache/os.h b/include/pistache/os.h index 5b7ad5bb3..684eba853 100644 --- a/include/pistache/os.h +++ b/include/pistache/os.h @@ -97,7 +97,11 @@ namespace Pistache constexpr TagValue value() const { return value_; } uint64_t valueU64() const { return ((uint64_t)value_); } - constexpr uint64_t actualFdU64Value() const +#ifndef _USE_LIBEVENT + constexpr +#endif + uint64_t + actualFdU64Value() const { #ifdef _USE_LIBEVENT if (value_ == NULL) diff --git a/include/pistache/peer.h b/include/pistache/peer.h index 936748d2e..477999bb7 100644 --- a/include/pistache/peer.h +++ b/include/pistache/peer.h @@ -52,7 +52,8 @@ namespace Pistache::Tcp const Address& address() const; const std::string& hostname(); - Fd fd() const; + Fd fd() const; // can return PS_FD_EMPTY + int actualFd() const; // can return -1 void closeFd(); diff --git a/include/pistache/pist_timelog.h b/include/pistache/pist_timelog.h index 58f660c8d..71a98ce60 100644 --- a/include/pistache/pist_timelog.h +++ b/include/pistache/pist_timelog.h @@ -30,8 +30,23 @@ // --------------------------------------------------------------------------- +#include // For _IS_BSD #include +#ifdef _IS_BSD +#define PS_STRLCPY(__dest, __src, __n) strlcpy(__dest, __src, __n) +#define PS_STRLCAT(__dest, __src, __size) strlcat(__dest, __src, __size) +#elif defined(__linux__) +#define PS_STRLCPY(__dest, __src, __n) strncpy(__dest, __src, __n) +#define PS_STRLCAT(__dest, __src, __size) strncat(__dest, __src, __size) +#elif defined(__APPLE__) +#define PS_STRLCPY(__dest, __src, __n) strlcpy(__dest, __src, __n) +#define PS_STRLCAT(__dest, __src, __size) strlcat(__dest, __src, __size) +#else +#define PS_STRLCPY(__dest, __src, __n) strcpy(__dest, __src) +#define PS_STRLCAT(__dest, __src, __size) strcat(__dest, __src) +#endif + #ifdef DEBUG #define PS_TIMINGS_DBG 1 #endif @@ -152,7 +167,7 @@ class __PS_TIMEDBG { for(unsigned int i=0; i<10; i++) marker_chars[i] = the_marker; - strcpy(&(marker_chars[10]), "..."); + PS_STRLCPY(&(marker_chars[10]), "...", 4); marker_chars += (10 + 3); for(unsigned int i=0; i<10; i++) @@ -227,7 +242,7 @@ class __PS_TIMEDBG va_end(ap); if (ln >= ((int) sizeof_buf)) - strcat(buf_ptr, "..."); + PS_STRLCAT(buf_ptr, "...", 5); return(buf_ptr); } diff --git a/include/pistache/transport.h b/include/pistache/transport.h index 887ae970b..a26009651 100644 --- a/include/pistache/transport.h +++ b/include/pistache/transport.h @@ -12,10 +12,10 @@ #pragma once -#include -#include #include #include +#include +#include #include #include @@ -59,8 +59,11 @@ namespace Pistache::Tcp #endif ) { - // Always enqueue reponses for sending. Giving preference to consumer - // context means chunked responses could be sent out of order. + // Always enqueue reponses for sending. Giving preference to + // consumer context means chunked responses could be sent out of + // order. + // + // Note: fd could be PS_FD_EMPTY return Async::Promise( [=](Async::Deferred deferred) mutable { BufferHolder holder { buffer }; @@ -110,6 +113,8 @@ namespace Pistache::Tcp } #endif + void closeFd(Fd fd); + // !!!! Make protected like removePeer void removeAllPeers(); // cleans up toWrite and does CLOSE_FD on each diff --git a/meson.build b/meson.build index c03092820..1a28f3a3b 100644 --- a/meson.build +++ b/meson.build @@ -21,8 +21,8 @@ fs = import('fs') #macOS host_machine.system() is 'darwin' -if host_machine.system() != 'linux' and host_machine.system() != 'darwin' - error('Pistache currenly only supports Linux and macOS. See https://github.com/pistacheio/pistache/issues/6#issuecomment-242398225 for more information') +if host_machine.system() != 'linux' and host_machine.system() != 'darwin' and not host_machine.system().endswith('bsd') + error('Pistache currenly only supports Linux, macOS and Free/Open/NetBSD. See https://github.com/pistacheio/pistache/issues/6#issuecomment-242398225 for more information') endif compiler = meson.get_compiler('cpp') @@ -133,11 +133,17 @@ if get_option('PISTACHE_USE_SSL') deps_libpistache += dependency('openssl') endif -if host_machine.system() == 'darwin' or get_option('PISTACHE_FORCE_LIBEVENT') +if host_machine.system() == 'darwin' or host_machine.system().endswith('bsd') or get_option('PISTACHE_FORCE_LIBEVENT') deps_libpistache += dependency('libevent') deps_libpistache += dependency('libevent_pthreads') endif +if host_machine.system().endswith('bsd') + # libexecinfo is included for the 'backtrace' function + libexecinfo_dep = compiler.find_library('execinfo') + deps_libpistache += libexecinfo_dep +endif + version_array = [] if meson.version().version_compare('>=0.57.0') version_array = fs.read('version.txt').strip().split('.') diff --git a/src/client/client.cc b/src/client/client.cc index 928d0ac1b..06a27968b 100644 --- a/src/client/client.cc +++ b/src/client/client.cc @@ -491,18 +491,19 @@ namespace Pistache::Http::Experimental PS_LOG_DEBUG_ARGS("Calling ::connect fs %d", GET_ACTUAL_FD(fd)); int res = ::connect(GET_ACTUAL_FD(fd), data->getAddr(), data->addr_len); - if (res == -1) + PS_LOG_DEBUG_ARGS("::connect res %d, errno on fail %d (%s)", + res, (res < 0) ? errno : 0, + (res < 0) ? strerror(errno) : "success"); + + if ((res == 0) || ((res == -1) && (errno == EINPROGRESS))) { - if (errno == EINPROGRESS) - { - reactor()->registerFdOneShot(key(), fd, - NotifyOn::Write | NotifyOn::Hangup | NotifyOn::Shutdown); - } - else - { - data->reject(Error::system("Failed to connect")); - continue; - } + reactor()->registerFdOneShot(key(), fd, + NotifyOn::Write | NotifyOn::Hangup | NotifyOn::Shutdown); + } + else + { + data->reject(Error::system("Failed to connect")); + continue; } connections.insert(std::make_pair(fd, std::move(*data))); } diff --git a/src/common/eventmeth.cc b/src/common/eventmeth.cc index 1832af0a5..bd5b114c9 100644 --- a/src/common/eventmeth.cc +++ b/src/common/eventmeth.cc @@ -11,6 +11,8 @@ #ifdef _USE_LIBEVENT +#include + /* ------------------------------------------------------------------------- */ /* * Event classes - EmEvent, EmEventCtr, EmEventFd and EmEventTmrFd @@ -1462,12 +1464,12 @@ namespace Pistache "EmEventCtr %p also being activated for write", this); - flags |= EV_WRITE; - } - - event_active(ev_, flags, 0 /* obsolete parm*/); - } + flags |= EV_WRITE; } + + event_active(ev_, flags, 0 /* obsolete parm*/); + } + } std::shared_ptr tmp_cv_sptr( @@ -1888,7 +1890,10 @@ EmEventTmrFd::EmEventTmrFd(clockid_t clock_id, break; case CLOCK_MONOTONIC: + #ifndef _IS_BSD + // CLOCK_MONOTONIC_RAW not defined on FreeBSD13.3 and OpenBSD 7.3 case CLOCK_MONOTONIC_RAW: + #endif #ifdef __APPLE__ case CLOCK_MONOTONIC_RAW_APPROX: case CLOCK_UPTIME_RAW: @@ -4234,7 +4239,7 @@ EmEventTmrFd::EmEventTmrFd(clockid_t clock_id, // To enable to_string of an Fd std::string to_string(const EmEvent * eme) - {return(std::to_string((unsigned long) eme));}; + {return(std::to_string((unsigned long) eme));} /* ------------------------------------------------------------------------- */ diff --git a/src/common/http.cc b/src/common/http.cc index dbafafe25..d4293605f 100644 --- a/src/common/http.cc +++ b/src/common/http.cc @@ -1180,7 +1180,7 @@ namespace Pistache::Http auto* transport = writer.transport_; auto peer = writer.peer(); - auto sockFd = peer->fd(); + auto sockFd = peer->fd(); // may be PS_FD_EMPTY auto buffer = buf->buffer(); return transport->asyncWrite(sockFd, buffer, diff --git a/src/common/http_defs.cc b/src/common/http_defs.cc index e12967566..f91742b3a 100644 --- a/src/common/http_defs.cc +++ b/src/common/http_defs.cc @@ -129,11 +129,11 @@ namespace Pistache::Http date::to_stream(os, "%a, %d %b %Y %T %Z", date_); break; case Type::RFC1123GMT: { - // Requires GMT so we must use std::gmtime to convert to GMT. - // We cannot use "%Z" since that refers to the local time zone name, + // Requires GMT so we must use std::gmtime to convert to GMT. We + // cannot use "%Z" since may refer to the local time zone name, // not the name associated with the std::tm object (since std::tm // isn't guaranteed to have a tm_zone field - it only does on - // POSIX.1-2024 systems). + // POSIX.1-2024 systems; this issue seen on NetBSD 10.0). time_t t = std::chrono::system_clock::to_time_t(date_); os << std::put_time(std::gmtime(&t), "%a, %d %b %Y %T GMT"); } diff --git a/src/common/peer.cc b/src/common/peer.cc index 51002ea5e..82b0c2e2d 100644 --- a/src/common/peer.cc +++ b/src/common/peer.cc @@ -19,8 +19,8 @@ #include #include -#include #include +#include namespace Pistache::Tcp { @@ -40,15 +40,13 @@ namespace Pistache::Tcp , ssl_(ssl) , id_(getUniqueId()) { - PS_LOG_DEBUG_ARGS("peer %p, fd %" PIST_QUOTE(PS_FD_PRNTFCD) - ", Address ptr %p, ssl %p", + PS_LOG_DEBUG_ARGS("peer %p, fd %" PIST_QUOTE(PS_FD_PRNTFCD) ", Address ptr %p, ssl %p", this, fd, &addr, ssl); } Peer::~Peer() { - PS_LOG_DEBUG_ARGS("peer %p, fd %" PIST_QUOTE(PS_FD_PRNTFCD) - ", Address ptr %p, ssl %p", + PS_LOG_DEBUG_ARGS("peer %p, fd %" PIST_QUOTE(PS_FD_PRNTFCD) ", Address ptr %p, ssl %p", this, fd_, &addr, ssl_); closeFd(); // does nothing if already closed @@ -117,24 +115,52 @@ namespace Pistache::Tcp Fd Peer::fd() const { - if (fd_ == PS_FD_EMPTY) + Fd res_fd(fd_); + + if (res_fd == PS_FD_EMPTY) + { + PS_LOG_DEBUG_ARGS("peer %p has no associated fd", this); + return (PS_FD_EMPTY); + } + + return res_fd; + } + + int Peer::actualFd() const // can return -1 + { + Fd this_fd(fd_); + + if (this_fd == PS_FD_EMPTY) { PS_LOG_DEBUG_ARGS("peer %p has no associated fd", this); - throw std::runtime_error("The peer has no associated fd"); + return (-1); } - return fd_; + return (GET_ACTUAL_FD(this_fd)); } void Peer::closeFd() { + PS_LOG_DEBUG_ARGS("peer %p, fd %" PIST_QUOTE(PS_FD_PRNTFCD), this, fd_); - - if (fd_ != PS_FD_EMPTY) + + auto this_fd = fd_; + + if (this_fd != PS_FD_EMPTY) { - CLOSE_FD(fd_); fd_ = PS_FD_EMPTY; + + if (transport_) + { + // Getting transport to do the close allows transport to clean + // up any transport usage of the Fd, e.g. in the write queue + transport_->closeFd(this_fd); + } + else + { + CLOSE_FD(this_fd); + } } } diff --git a/src/common/reactor.cc b/src/common/reactor.cc index dff153fa9..54e699ebf 100644 --- a/src/common/reactor.cc +++ b/src/common/reactor.cc @@ -23,6 +23,14 @@ #include #include +#ifdef _IS_BSD +// For pthread_set_name_np +#include +#ifndef __NetBSD__ +#include +#endif +#endif + using namespace std::string_literals; namespace Pistache::Aio @@ -625,7 +633,11 @@ namespace Pistache::Aio thread = std::thread([=]() { if (!threadsName_.empty()) { +#if defined _IS_BSD && ! defined __NetBSD__ + pthread_set_name_np( +#else pthread_setname_np( +#endif #ifndef __APPLE__ // Apple's macOS version of pthread_setname_np // takes only "const char * name" as parm @@ -637,6 +649,10 @@ namespace Pistache::Aio // as per macOS, while newer FreeBSD (2021 ?) // behaves as per Linux pthread_self(), +#endif +#ifdef __NetBSD__ + "%s", //NetBSD has 3 parms for pthread_setname_np + (void *) /*cast away const for NetBSD*/ #endif threadsName_.substr(0, 15).c_str()); } diff --git a/src/common/transport.cc b/src/common/transport.cc index 63bbaeeeb..8af245bf3 100644 --- a/src/common/transport.cc +++ b/src/common/transport.cc @@ -25,6 +25,10 @@ #include #endif +#ifdef _IS_BSD +#include // for lseek +#endif + #ifndef _USE_LIBEVENT_LIKE_APPLE // Note: sys/timerfd.h is linux-only (and certainly POSIX only) #include @@ -123,11 +127,16 @@ namespace Pistache::Tcp { handlePeer(peer); } + + Guard guard(toWriteLock); Fd fd = peer->fd(); + if (fd == PS_FD_EMPTY) { - Guard guard(toWriteLock); - toWrite.emplace(fd, std::deque {}); + PS_LOG_DEBUG("Empty Fd"); + return; } + + toWrite.emplace(fd, std::deque {}); } #ifdef DEBUG @@ -265,10 +274,21 @@ namespace Pistache::Tcp void Transport::handleIncoming(const std::shared_ptr& peer) { + if (!peer) + { + PS_LOG_DEBUG("Null peer"); + return; + } + char buffer[Const::MaxBuffer] = { 0 }; ssize_t totalBytes = 0; - int fdactual = GET_ACTUAL_FD(peer->fd()); + int fdactual = peer->actualFd(); + if (fdactual < 0) + { + PS_LOG_DEBUG_ARGS("Peer %p has no actual Fd", peer.get()); + return; + } for (;;) { @@ -337,7 +357,11 @@ namespace Pistache::Tcp void Transport::removePeer(const std::shared_ptr& peer) { Fd fd = peer->fd(); - + if (fd == PS_FD_EMPTY) + { + PS_LOG_DEBUG("Empty Fd"); + return; + } { // See comment in transport.h on why peers_ must be mutex-protected std::lock_guard l_guard(peers_mutex_); @@ -353,12 +377,6 @@ namespace Pistache::Tcp } } - { - // Clean up buffers - Guard guard(toWriteLock); - toWrite.erase(fd); - } - // Don't rely on close deleting this FD from the epoll "interest" list. // This is needed in case the FD has been shared with another process. // Sharing should no longer happen by accident as SOCK_CLOEXEC is now set on @@ -372,10 +390,24 @@ namespace Pistache::Tcp peer->closeFd(); } + void Transport::closeFd(Fd fd) + { + if (fd == PS_FD_EMPTY) + { + PS_LOG_DEBUG("Trying to close empty Fd"); + return; + } + + Guard guard(toWriteLock); + toWrite.erase(fd); // Clean up write buffers + + CLOSE_FD(fd); + } + void Transport::removeAllPeers() { PS_TIMEDBG_START_THIS; - + for (;;) { std::shared_ptr peer; @@ -395,7 +427,7 @@ namespace Pistache::Tcp } } - removePeer(peer);// removePeer locks mutex, erases peer from peers_ + removePeer(peer); // removePeer locks mutex, erases peer from peers_ } } @@ -546,13 +578,26 @@ namespace Pistache::Tcp { int tcp_no_push = 0; socklen_t len = sizeof(tcp_no_push); - int sock_opt_res = getsockopt(GET_ACTUAL_FD(fd), tcp_prot_num_, -#ifdef __APPLE__ - TCP_NOPUSH, + int sock_opt_res = -1; + +#ifdef __NetBSD__ + { // encapsulate + int tcp_nodelay = 0; + sock_opt_res = getsockopt(GET_ACTUAL_FD(fd), tcp_prot_num_, + TCP_NODELAY, &tcp_nodelay, &len); + if (sock_opt_res == 0) + tcp_no_push = !tcp_nodelay; + } +#else + sock_opt_res = getsockopt(GET_ACTUAL_FD(fd), tcp_prot_num_, +#if defined __APPLE__ || defined _IS_BSD + TCP_NOPUSH, #else - TCP_CORK, + TCP_CORK, #endif - &tcp_no_push, &len); + &tcp_no_push, &len); +#endif // of ifdef __NetBSD__ ... else + if (sock_opt_res == 0) { if (((tcp_no_push == 0) && (msg_more_style)) || ((tcp_no_push != 0) && (!msg_more_style))) @@ -560,14 +605,29 @@ namespace Pistache::Tcp PS_LOG_DEBUG_ARGS("Setting MSG_MORE style to %s", (msg_more_style) ? "on" : "off"); + int optval = +#ifdef __NetBSD__ + // In NetBSD case we're getting/setting (or resetting) the + // TCP_NODELAY socket option, which _stops_ data being held + // prior to send, whereas in Linux, macOS, FreeBSD or + // OpenBSD we're using TCP_CORK/TCP_NOPUSH which may + // _cause_ data to be held prior to send. I.e. they're + // opposites. + msg_more_style ? 0 : 1; +#else + msg_more_style ? 1 : 0; +#endif + tcp_no_push = msg_more_style ? 1 : 0; sock_opt_res = setsockopt(GET_ACTUAL_FD(fd), tcp_prot_num_, -#ifdef __APPLE__ +#ifdef __NetBSD__ + TCP_NODELAY, +#elif defined __APPLE__ || defined _IS_BSD TCP_NOPUSH, #else TCP_CORK, #endif - &tcp_no_push, len); + &optval, len); if (sock_opt_res < 0) throw std::runtime_error("setsockopt failed"); } @@ -579,14 +639,13 @@ namespace Pistache::Tcp } #endif } -#ifdef DEBUG else { - PS_LOG_DEBUG_ARGS("getsockopt failed for fd %p, actual fd %d", - fd, GET_ACTUAL_FD(fd)); + PS_LOG_DEBUG_ARGS("getsockopt failed for fd %p, actual fd %d, " + "errno %d, err %s", + fd, GET_ACTUAL_FD(fd), errno, strerror(errno)); throw std::runtime_error("getsockopt failed"); } -#endif } #endif // of ifdef _USE_LIBEVENT_LIKE_APPLE @@ -648,6 +707,136 @@ namespace Pistache::Tcp return bytesWritten; } +#ifdef _IS_BSD + // This is the sendfile function prototype found in Linux. However, + // sendfile does not exist in OpenBSD, so we make our own. + // + // https://www.man7.org/linux/man-pages/man2/sendfile.2.html + // Copies FROM "in_fd" TO "out_fd" + // Returns number of bytes written on success, -1 with errno set on error + // + // If offset is not NULL, then sendfile() does not modify the file offset + // of in_fd; otherwise the file offset is adjusted to reflect the number of + // bytes read from in_fd. + ssize_t my_sendfile(int out_fd, int in_fd, off_t* offset, size_t count) + { + char buff[65536 + 16]; // 64KB is an efficient size for read/write + // blocks in most storage systems. The 16 bytes + // is to reduce risk of buffer overflow in the + // events of a bug. + + int read_errors = 0; + int write_errors = 0; + ssize_t bytes_written_res = 0; + + off_t in_fd_start_pos = -1; + + if (offset) + { + in_fd_start_pos = lseek(in_fd, 0, SEEK_CUR); + if (in_fd_start_pos < 0) + { + PS_LOG_DEBUG("lseek error"); + return (in_fd_start_pos); + } + + if (lseek(in_fd, *offset, SEEK_SET) < 0) + { + PS_LOG_DEBUG("lseek error"); + return (-1); + } + } + + for (;;) + { + size_t bytes_to_read = count ? std::min(sizeof(buff) - 16, count) : (sizeof(buff) - 16); + + ssize_t bytes_read = read(in_fd, &(buff[0]), bytes_to_read); + if (bytes_read == 0) // End of file + break; + + if (bytes_read < 0) + { + if ((errno == EINTR) || (errno == EAGAIN)) + { + PS_LOG_DEBUG("read-interrupted error"); + + read_errors++; + if (read_errors < 256) + continue; + + PS_LOG_DEBUG("read-interrupted repeatedly error"); + errno = EIO; + } + + bytes_written_res = -1; + break; + } + read_errors = 0; + + bool re_adjust_pos = false; + + if ((count) && (bytes_read > ((ssize_t)count))) + { + bytes_read = ((ssize_t)count); + re_adjust_pos = true; + } + + if (offset) + { + *offset += bytes_read; + if (re_adjust_pos) + lseek(in_fd, *offset, SEEK_SET); + } + + auto p = &(buff[0]); + while (bytes_read > 0) + { + ssize_t bytes_written = write(out_fd, p, bytes_read); + if (bytes_written <= 0) + { + if ((bytes_written == 0) || (errno == EINTR) || (errno == EAGAIN)) + { + PS_LOG_DEBUG("write-interrupted error"); + + write_errors++; + if (write_errors < 256) + continue; + + PS_LOG_DEBUG("write-interrupted repeatedly error"); + errno = EIO; + } + + bytes_written_res = -1; + break; + } + write_errors = 0; + + bytes_read -= bytes_written; + p += bytes_written; + bytes_written_res += bytes_written; + } + + if (count) + count -= bytes_read; + } + + // if offset non null, set in_fd file pos to pos from start of this + // function + if ((offset) && (bytes_written_res >= 0) && (lseek(in_fd, in_fd_start_pos, SEEK_SET) < 0)) + { + PS_LOG_DEBUG("lseek error"); + bytes_written_res = -1; + } + + return (bytes_written_res); + } + +#define SENDFILE my_sendfile +#else +#define SENDFILE ::sendfile +#endif // ifdef _IS_BSD + ssize_t Transport::sendFile(Fd fd, int file, off_t offset, size_t len) { ssize_t bytesWritten = 0; @@ -683,11 +872,16 @@ namespace Pistache::Tcp if (it_second_ssl_is_null) { #endif /* PISTACHE_USE_SSL */ - PS_LOG_DEBUG_ARGS("::sendfile fd %" PIST_QUOTE(PS_FD_PRNTFCD) " actual-fd %d, file fd, len %d", - fd, GET_ACTUAL_FD(fd), file, len); + +#ifdef DEBUG + const char* sendfile_fn_name = PIST_QUOTE(SENDFILE); +#endif + PS_LOG_DEBUG_ARGS( + "%s fd %" PIST_QUOTE(PS_FD_PRNTFCD) " actual-fd %d, file fd %d, len %d", sendfile_fn_name, + fd, GET_ACTUAL_FD(fd), file, len); #ifdef _USE_LIBEVENT_LIKE_APPLE - // !!!! Should we do configureMsgMoreStyle for SLL as well? And + // !!!! Should we do configureMsgMoreStyle for SSL as well? And // same question in sendRawBuffer configureMsgMoreStyle(fd, false /*msg_more_style*/); #endif @@ -720,10 +914,12 @@ namespace Pistache::Tcp } #else - bytesWritten = ::sendfile(GET_ACTUAL_FD(fd), file, &offset, len); + bytesWritten = SENDFILE(GET_ACTUAL_FD(fd), file, &offset, len); #endif - PS_LOG_DEBUG_ARGS("::sendfile fd %" PIST_QUOTE(PS_FD_PRNTFCD) " , bytesWritten %d", fd, bytesWritten); + PS_LOG_DEBUG_ARGS( + "%s fd %" PIST_QUOTE(PS_FD_PRNTFCD) ", bytesWritten %d", + sendfile_fn_name, fd, bytesWritten); #ifdef PISTACHE_USE_SSL } @@ -832,6 +1028,8 @@ namespace Pistache::Tcp break; auto fd = write->peerFd; + if (fd == PS_FD_EMPTY) + continue; if (!isPeerFd(fd)) continue; @@ -882,6 +1080,11 @@ namespace Pistache::Tcp PS_TIMEDBG_START_THIS; Fd fd = peer->fd(); + if (fd == PS_FD_EMPTY) + { + PS_LOG_DEBUG("Empty Fd"); + return; + } { // See comment in transport.h on why peers_ must be mutex-protected diff --git a/src/server/listener.cc b/src/server/listener.cc index 3e0a06dcc..410c79af9 100644 --- a/src/server/listener.cc +++ b/src/server/listener.cc @@ -202,12 +202,15 @@ namespace Pistache::Tcp if (options.hasFlag(Options::ReuseAddr)) { + PS_LOG_DEBUG("Set SO_REUSEADDR"); + int one = 1; TRY(::setsockopt(actualFd, SOL_SOCKET, SO_REUSEADDR, &one, sizeof(one))); } if (options.hasFlag(Options::ReusePort)) { + PS_LOG_DEBUG("Set SO_REUSEPORT"); int one = 1; TRY(::setsockopt(actualFd, SOL_SOCKET, SO_REUSEPORT, &one, sizeof(one))); } @@ -875,8 +878,39 @@ namespace Pistache::Tcp { PS_TIMEDBG_START_THIS; + if (!peer) + { + PS_LOG_DEBUG("Null peer"); + return; + } + + // There is some risk that the Fd belonging to the peer could be closed + // in another thread before this dispatchPeer routine completes. In + // particular, that has been seen to happen occasionally in + // rest_server_test.response_status_code_test in OpenBSD. + // + // To guard against that, we simply need to check for an invalid Fd. We + // also check for an invalid actual-fd for safety's sake. + + int actual_fd = -1; + try + { + actual_fd = peer->actualFd(); + } + catch (...) + { + PS_LOG_INFO_ARGS("Failed to get actual fd from peer %p", + peer.get()); + return; + } + if (actual_fd == -1) + { + PS_LOG_INFO_ARGS("No actual fd for peer %p", peer.get()); + return; + } + auto handlers = reactor_->handlers(transportKey); - auto idx = (GET_ACTUAL_FD(peer->fd())) % handlers.size(); + auto idx = actual_fd % handlers.size(); auto transport = std::static_pointer_cast(handlers[idx]); transport->handleNewPeer(peer); diff --git a/tests/headers_test.cc b/tests/headers_test.cc index 617c167c3..434029951 100644 --- a/tests/headers_test.cc +++ b/tests/headers_test.cc @@ -739,13 +739,23 @@ TEST(headers_test, access_control_allow_methods_test) TEST(headers_test, last_modified_test) { - const std::string ref = "Sun, 06 Nov 1994 08:49:37 GMT"; + // const std::string ref = "Sun, 06 Nov 1994 08:49:37 GMT"; using namespace std::chrono; Pistache::Http::FullDate::time_point expected_time_point = date::sys_days(date::year { 1994 } / 11 / 6) + hours(8) + minutes(49) + seconds(37); Pistache::Http::FullDate fd(expected_time_point); Pistache::Http::Header::LastModified l0(fd); std::ostringstream oss; l0.write(oss); + + // As of July/2024, it seems that in macOS, Linux and OpenBSD this produces + // an OSS ending "GMT", while in FreeBSD it ends "UTC". Of course, they + // mean the same thing, and we allow either. + const bool oss_ends_utc = + ((oss.str().length() >= 3) && + (oss.str().compare(oss.str().length() - 3, 3, "UTC") == 0)); + const std::string ref(std::string("Sun, 06 Nov 1994 08:49:37 ") + + (oss_ends_utc ? "UTC" : "GMT")); + ASSERT_EQ(ref, oss.str()); Pistache::Http::Header::LastModified l1; l1.parse(ref); diff --git a/tests/helpers/fd_utils.cc b/tests/helpers/fd_utils.cc index 601f1dae9..c8b700f59 100644 --- a/tests/helpers/fd_utils.cc +++ b/tests/helpers/fd_utils.cc @@ -31,6 +31,15 @@ namespace filesystem = std::experimental::filesystem; // // Return: if buffer non-null, number of proc_fdinfo written, -1 on fail +#elif !defined __linux__ +#include // for sysconf + +// For getrlimit +#include // Required in FreeBSD+Open+NetBSD +#include // recommended in FreeBSD, optional for Open+NetBSD +#include // recommended in FreeBSD, optional for Open+NetBSD + +#include // for memset #endif namespace Pistache @@ -67,7 +76,7 @@ namespace Pistache return ((std::size_t)num_fds); } -#else +#elif defined __linux__ using filesystem::directory_iterator; const filesystem::path fds_dir { "/proc/self/fd" }; @@ -78,8 +87,45 @@ namespace Pistache return std::distance(directory_iterator(fds_dir), directory_iterator {}); +#else // fallback case, e.g. *BSD +#ifndef OPEN_MAX +#define OPEN_MAX 4096 +#endif + // Be careful with portability here. rl.rlim_cur, of type rlim_t, seems + // to be an int on FreeBSD, but a wider data type on OpenBSD ("long + // long" ?). It is signed on FreeBSD and NetBSD, but unsigned on + // OpenBSD. + long maxfd; + + maxfd = sysconf(_SC_OPEN_MAX); + if (maxfd < 0) // or if sysconf not defined at all + { + struct rlimit rl; + memset(&rl, 0, sizeof(rl)); + int getrlimit_res = getrlimit(RLIMIT_NOFILE, &rl); + if (getrlimit_res == 0) + { + maxfd = (long)(rl.rlim_cur < 2 * OPEN_MAX) ? rl.rlim_cur : 2 * OPEN_MAX; + if (maxfd == 0) + maxfd = -1; + } + } + + if ((maxfd < 0) || (maxfd > 4 * OPEN_MAX)) + maxfd = OPEN_MAX; + + long j, n = 0; + for (j = 0; j < maxfd; j++) + { + int fd = dup((int)j); + if (fd < 0) + continue; + n++; + close(fd); + } -#endif // of ifdef... else... __APPLE__ + return (n); +#endif // of ifdef... elif... else... __APPLE__ } } // namespace Pistache diff --git a/tests/http_server_test.cc b/tests/http_server_test.cc index 9b5170622..bbfb984c7 100644 --- a/tests/http_server_test.cc +++ b/tests/http_server_test.cc @@ -476,7 +476,7 @@ TEST(http_server_test, many_client_with_requests_to_multithreaded_server) LOGGER("test", "Server address: " << server_address); const int NO_TIMEOUT = 0; - const int SECONDS_TIMOUT = 12; + const int SECONDS_TIMOUT = 20; const int FIRST_CLIENT_REQUEST_SIZE = 128; std::future result1(std::async(clientLogicFunc, FIRST_CLIENT_REQUEST_SIZE, server_address, @@ -484,7 +484,7 @@ TEST(http_server_test, many_client_with_requests_to_multithreaded_server) const int SECOND_CLIENT_REQUEST_SIZE = 192; std::future result2( std::async(clientLogicFunc, SECOND_CLIENT_REQUEST_SIZE, server_address, - NO_TIMEOUT, 2 * SECONDS_TIMOUT)); + NO_TIMEOUT, 3 * SECONDS_TIMOUT)); int res1 = result1.get(); int res2 = result2.get(); diff --git a/tests/listener_test.cc b/tests/listener_test.cc index 8ff5c3295..706bc37df 100644 --- a/tests/listener_test.cc +++ b/tests/listener_test.cc @@ -21,6 +21,11 @@ #include #include #include + +#ifdef _IS_BSD +#include // for wait +#endif + class SocketWrapper { @@ -77,48 +82,57 @@ class DummyHandler : public Pistache::Http::Handler /* * Will try to get a free port by binding port 0. */ -SocketWrapper bind_free_port() +SocketWrapper bind_free_port_helper(int ai_family) { PS_TIMEDBG_START; - int sockfd; // listen on sock_fd, new connection on new_fd + int sockfd = -1; // listen on sock_fd, new connection on new_fd struct addrinfo hints = {}, *servinfo, *p; int yes = 1; int rv; - hints.ai_family = AF_UNSPEC; + hints.ai_family = ai_family; hints.ai_socktype = SOCK_STREAM; hints.ai_flags = AI_PASSIVE; // use my IP if ((rv = getaddrinfo(nullptr, "0", &hints, &servinfo)) != 0) { - std::cerr << "getaddrinfo: " << gai_strerror(rv) << "\n"; - exit(1); + if (ai_family == AF_UNSPEC) + { + std::cerr << "getaddrinfo: " << gai_strerror(rv) << "\n"; + exit(1); + } + throw std::runtime_error("getaddrinfo fail"); } - // loop through all the results and bind to the first we can for (p = servinfo; p != nullptr; p = p->ai_next) { if ((sockfd = socket(p->ai_family, p->ai_socktype, p->ai_protocol)) == -1) { PS_LOG_DEBUG("server: socket"); - perror("server: socket"); + if (ai_family == AF_UNSPEC) + perror("server: socket"); continue; } if (setsockopt(sockfd, SOL_SOCKET, SO_REUSEADDR, &yes, sizeof(int)) == -1) { PS_LOG_DEBUG("setsockopt"); - perror("setsockopt"); - exit(1); + if (ai_family == AF_UNSPEC) + { + perror("setsockopt"); + exit(1); + } + throw std::runtime_error("setsockopt fail"); } if (bind(sockfd, p->ai_addr, p->ai_addrlen) == -1) { PS_LOG_DEBUG_ARGS("server: bind failed, sockfd %d", sockfd); close(sockfd); - perror("server: bind"); + if (ai_family == AF_UNSPEC) + perror("server: bind"); continue; } @@ -127,14 +141,42 @@ SocketWrapper bind_free_port() freeaddrinfo(servinfo); // all done with this structure - if (p == nullptr) + if (ai_family == AF_UNSPEC) { - fprintf(stderr, "server: failed to bind\n"); - exit(1); + if (p == nullptr) + { + fprintf(stderr, "server: failed to bind\n"); + exit(1); + } + throw std::runtime_error("failed to bind"); } + return SocketWrapper(sockfd); } +/* + * Will try to get a free port by binding port 0. + */ +SocketWrapper bind_free_port() +{ + // As of July/2024, in Linux and macOS, using AF_UNSPEC leads us to use + // IPv4 when available. However, in FreeBSD, it causes us to use IPv6 when + // available. Since Pistache itself defaults to IPv4, we try IPv4 first for + // bind_free_port_helper, and only try AF_UNSPEC if IPv4 fails. + + try + { + return(bind_free_port_helper(AF_INET/*IPv4*/)); + } + catch(...) + { + PS_LOG_DEBUG("bind_free_port_helper failed for IPv4"); + } + + return(bind_free_port_helper(AF_UNSPEC/*any*/)); +} + + // This is just done to get the value of a free port. The socket will be // closed after the closing curly bracket and the port will be free again // (SO_REUSEADDR option). In theory, it is possible that some application grab diff --git a/tests/rest_server_test.cc b/tests/rest_server_test.cc index 4958b525a..1cdc67bd6 100644 --- a/tests/rest_server_test.cc +++ b/tests/rest_server_test.cc @@ -110,10 +110,24 @@ TEST(rest_server_test, basic_test) { ASSERT_EQ(res->body, "ip6-localhost"); // count the passing test. } - else + else if (res->body == "localhost") { ASSERT_EQ(res->body, "localhost"); } + else + { + const unsigned int my_max_hostname_len = 1024; + + // NetBSD showed this case, when hostname was not "localhost" + char name[my_max_hostname_len + 6]; + name[0] = 0; + + int ghn_res = gethostname(&(name[0]), my_max_hostname_len+2); + ASSERT_EQ(ghn_res, 0); + + ASSERT_EQ(res->body, &(name[0])); + } + stats.shutdown(); } diff --git a/version.txt b/version.txt index f9e52323c..8ef806eb2 100644 --- a/version.txt +++ b/version.txt @@ -1 +1 @@ -0.3.1.20240626 +0.4.1.20240804