Index: configure.ac ================================================================== --- configure.ac +++ configure.ac @@ -1331,10 +1331,13 @@ AC_CHECK_HEADERS([arpa/inet.h netdb.h]) AC_CHECK_HEADER(netipx/ipx.h, [ AC_DEFINE(OF_HAVE_NETIPX_IPX_H, 1, [Whether we have netipx/ipx.h]) ]) + AC_CHECK_HEADERS(sys/un.h, [ + AC_DEFINE(OF_HAVE_SYS_UN_H, 1, [Whether we have sys/un.h]) + ]) AC_CHECK_MEMBER([struct sockaddr_in6.sin6_addr], [ AC_EGREP_CPP(egrep_cpp_yes, [ #ifdef _WIN32 typedef int BOOL; @@ -1455,10 +1458,40 @@ ], [ AC_DEFINE(OF_HAVE_IPX, 1, [Whether we have IPX/SPX]) AC_SUBST(USE_SRCS_IPX, '${SRCS_IPX}') ]) ]) + + AC_CHECK_HEADERS(afunix.h, [ + AC_DEFINE(OF_HAVE_AFUNIX_H, 1, [Whether we have afunix.h]) + ], [], [ + #ifdef _WIN32 + # include + #endif + ]) + AC_CHECK_MEMBER(struct sockaddr_un.sun_path, [ + AC_DEFINE(OF_HAVE_UNIX_SOCKETS, 1, + [Whether we have UNIX sockets]) + AC_SUBST(USE_SRCS_UNIX_SOCKETS, '${SRCS_UNIX_SOCKETS}') + ], [], [ + #ifdef OF_HAVE_SYS_TYPES_H + # include + #endif + #ifdef OF_HAVE_SYS_UN_H + # include + #endif + #ifdef _WIN32 + # include + #endif + #ifdef HAVE_AFUNIX_H + # include + #endif + + #ifdef __morphos__ + # error MorphOS has the struct but does not support it + #endif + ]) AC_CHECK_FUNCS(paccept accept4, break) AC_CHECK_FUNCS(kqueue1 kqueue, [ AC_DEFINE(HAVE_KQUEUE, 1, [Whether we have kqueue]) Index: extra.mk.in ================================================================== --- extra.mk.in +++ extra.mk.in @@ -70,7 +70,8 @@ USE_SRCS_FILES = @USE_SRCS_FILES@ USE_SRCS_IPX = @USE_SRCS_IPX@ USE_SRCS_PLUGINS = @USE_SRCS_PLUGINS@ USE_SRCS_SOCKETS = @USE_SRCS_SOCKETS@ USE_SRCS_THREADS = @USE_SRCS_THREADS@ +USE_SRCS_UNIX_SOCKETS = @USE_SRCS_UNIX_SOCKETS@ USE_SRCS_WINDOWS = @USE_SRCS_WINDOWS@ WRAPPER = @WRAPPER@ Index: src/Makefile ================================================================== --- src/Makefile +++ src/Makefile @@ -119,13 +119,10 @@ SRCS_FILES = OFFile.m \ OFINICategory.m \ OFINIFile.m \ OFSettings.m \ OFString+PathAdditions.m -SRCS_IPX = OFIPXSocket.m \ - OFSPXSocket.m \ - OFSPXStreamSocket.m SRCS_PLUGINS = OFPlugin.m SRCS_SOCKETS = OFDNSQuery.m \ OFDNSResolver.m \ OFDNSResourceRecord.m \ OFDNSResponse.m \ @@ -139,11 +136,17 @@ OFSequencedPacketSocket.m \ OFSocket.m \ OFStreamSocket.m \ OFTCPSocket.m \ OFUDPSocket.m \ - ${USE_SRCS_IPX} + ${USE_SRCS_IPX} \ + ${USE_SRCS_UNIX_SOCKETS} +SRCS_IPX = OFIPXSocket.m \ + OFSPXSocket.m \ + OFSPXStreamSocket.m +SRCS_UNIX_SOCKETS = OFUNIXDatagramSocket.m \ + OFUNIXStreamSocket.m SRCS_THREADS = OFCondition.m \ OFMutex.m \ OFPlainCondition.m \ OFPlainMutex.m \ OFPlainThread.m \ Index: src/OFDatagramSocket.m ================================================================== --- src/OFDatagramSocket.m +++ src/OFDatagramSocket.m @@ -205,10 +205,15 @@ #endif #ifdef OF_HAVE_IPX case AF_IPX: sender->family = OFSocketAddressFamilyIPX; break; +#endif +#ifdef OF_HAVE_UNIX_SOCKETS + case AF_UNIX: + sender->family = OFSocketAddressFamilyUNIX; + break; #endif default: sender->family = OFSocketAddressFamilyUnknown; break; } Index: src/OFSocket.h ================================================================== --- src/OFSocket.h +++ src/OFSocket.h @@ -36,10 +36,16 @@ # include #endif #ifdef OF_HAVE_NETIPX_IPX_H # include #endif +#ifdef OF_HAVE_SYS_UN_H +# include +#endif +#ifdef OF_HAVE_AFUNIX_H +# include +#endif #ifdef OF_WINDOWS # include # include # ifdef OF_HAVE_IPX @@ -66,10 +72,14 @@ static const OFSocketHandle OFInvalidSocketHandle = -1; #else typedef SOCKET OFSocketHandle; static const OFSocketHandle OFInvalidSocketHandle = INVALID_SOCKET; #endif + +#ifdef OF_WINDOWS +typedef short sa_family_t; +#endif #ifdef OF_WII typedef u8 sa_family_t; #endif @@ -89,10 +99,12 @@ OFSocketAddressFamilyIPv4, /** IPv6 */ OFSocketAddressFamilyIPv6, /** IPX */ OFSocketAddressFamilyIPX, + /** UNIX */ + OFSocketAddressFamilyUNIX, /** Any address family */ OFSocketAddressFamilyAny = 255 } OFSocketAddressFamily; #ifndef OF_HAVE_IPV6 @@ -123,10 +135,17 @@ # define sipx_network sa_netnum # define sipx_node sa_nodenum # define sipx_port sa_socket #endif +#if !defined(OF_HAVE_UNIX_SOCKETS) && !defined(OF_MORPHOS) +struct sockaddr_un { + sa_family_t sun_family; + char sun_path[108]; +}; +#endif + /** * @struct OFSocketAddress OFSocket.h ObjFW/OFSocket.h * * @brief A struct which represents a host / port pair for a socket. */ @@ -142,10 +161,11 @@ union { struct sockaddr sockaddr; struct sockaddr_in in; struct sockaddr_in6 in6; struct sockaddr_ipx ipx; + struct sockaddr_un un; } sockaddr; socklen_t length; } OFSocketAddress; #ifdef __cplusplus @@ -178,20 +198,29 @@ * @return The parsed IPv6 and port as an OFSocketAddress */ extern OFSocketAddress OFSocketAddressParseIPv6(OFString *IP, uint16_t port); /** - * @brief Creates an IPX address for the specified network, node and port. + * @brief Creates an IPX address for the specified node, network and port. * * @param node The node in the IPX network * @param network The IPX network * @param port The IPX port (sometimes called socket number) on the node + * @return An IPX socket address with the specified node, network and port. */ extern OFSocketAddress OFSocketAddressMakeIPX( const unsigned char node[_Nonnull IPX_NODE_LEN], uint32_t network, uint16_t port); +/** + * @brief Creates a UNIX socket address from the specified path. + * + * @param path The path of the UNIX socket + * @return A UNIX socket address with the specified path + */ +extern OFSocketAddress OFSocketAddressMakeUNIX(OFString *path); + /** * @brief Compares two OFSocketAddress for equality. * * @param address1 The address to compare with the second address * @param address2 The second address @@ -271,10 +300,19 @@ * @param node A byte array to store the IPX node of the address */ extern void OFSocketAddressIPXNode(const OFSocketAddress *_Nonnull address, unsigned char node[_Nonnull IPX_NODE_LEN]); +/** + * @brief Gets the UNIX socket path of the specified @ref OFSocketAddress. + * + * @param address The address on which to get the UNIX socket path + * @return The UNIX socket path + */ +extern OFString *_Nullable OFSocketAddressUNIXPath( + const OFSocketAddress *_Nonnull address); + extern bool OFSocketInit(void); #if defined(OF_HAVE_THREADS) && defined(OF_AMIGAOS) && !defined(OF_MORPHOS) extern void OFSocketDeinit(void); #endif extern int OFSocketErrNo(void); Index: src/OFSocket.m ================================================================== --- src/OFSocket.m +++ src/OFSocket.m @@ -43,10 +43,11 @@ #import "OFException.h" /* For some E* -> WSAE* defines */ #import "OFInitializationFailedException.h" #import "OFInvalidArgumentException.h" #import "OFInvalidFormatException.h" #import "OFLockFailedException.h" +#import "OFOutOfRangeException.h" #import "OFUnlockFailedException.h" #ifdef OF_AMIGAOS # include #endif @@ -534,18 +535,50 @@ sizeof(ret.sockaddr.ipx.sipx_network)); ret.sockaddr.ipx.sipx_port = OFToBigEndian16(port); return ret; } + +OFSocketAddress +OFSocketAddressMakeUNIX(OFString *path) +{ + void *pool = objc_autoreleasePoolPush(); + OFStringEncoding encoding = [OFLocale encoding]; + size_t length = [path cStringLengthWithEncoding: encoding]; + OFSocketAddress ret; + + if (length > sizeof(ret.sockaddr.un.sun_path)) + @throw [OFOutOfRangeException exception]; + + memset(&ret, '\0', sizeof(ret)); + ret.family = OFSocketAddressFamilyUNIX; + ret.length = (socklen_t)(sizeof(ret.sockaddr.un) - + (sizeof(ret.sockaddr.un.sun_path) - length)); + +#ifdef AF_UNIX + ret.sockaddr.un.sun_family = AF_UNIX; +#else + ret.sockaddr.un.sun_family = AF_UNSPEC; +#endif + memcpy(ret.sockaddr.un.sun_path, + [path cStringWithEncoding: encoding], length); + + objc_autoreleasePoolPop(pool); + + return ret; +} bool OFSocketAddressEqual(const OFSocketAddress *address1, const OFSocketAddress *address2) { const struct sockaddr_in *addrIn1, *addrIn2; const struct sockaddr_in6 *addrIn6_1, *addrIn6_2; const struct sockaddr_ipx *addrIPX1, *addrIPX2; + void *pool; + OFString *path1, *path2; + bool ret; if (address1->family != address2->family) return false; switch (address1->family) { @@ -565,11 +598,11 @@ if (addrIn1->sin_port != addrIn2->sin_port) return false; if (addrIn1->sin_addr.s_addr != addrIn2->sin_addr.s_addr) return false; - break; + return true; case OFSocketAddressFamilyIPv6: if (address1->length < (socklen_t)sizeof(struct sockaddr_in6) || address2->length < (socklen_t)sizeof(struct sockaddr_in6)) @throw [OFInvalidArgumentException exception]; @@ -581,11 +614,11 @@ if (memcmp(addrIn6_1->sin6_addr.s6_addr, addrIn6_2->sin6_addr.s6_addr, sizeof(addrIn6_1->sin6_addr.s6_addr)) != 0) return false; - break; + return true; case OFSocketAddressFamilyIPX: if (address1->length < (socklen_t)sizeof(struct sockaddr_ipx) || address2->length < (socklen_t)sizeof(struct sockaddr_ipx)) @throw [OFInvalidArgumentException exception]; @@ -599,16 +632,31 @@ return false; if (memcmp(addrIPX1->sipx_node, addrIPX2->sipx_node, IPX_NODE_LEN) != 0) return false; - break; + return true; + case OFSocketAddressFamilyUNIX: + pool = objc_autoreleasePoolPush(); + + path1 = OFSocketAddressUNIXPath(address1); + path2 = OFSocketAddressUNIXPath(address2); + + if (path1 == nil || path2 == nil) { + objc_autoreleasePoolPop(pool); + + return false; + } + + ret = [path1 isEqual: path2]; + + objc_autoreleasePoolPop(pool); + + return ret; default: @throw [OFInvalidArgumentException exception]; } - - return true; } unsigned long OFSocketAddressHash(const OFSocketAddress *address) { @@ -666,10 +714,19 @@ for (size_t i = 0; i < IPX_NODE_LEN; i++) OFHashAdd(&hash, address->sockaddr.ipx.sipx_node[i]); break; + case OFSocketAddressFamilyUNIX:; + void *pool = objc_autoreleasePoolPush(); + OFString *path = OFSocketAddressUNIXPath(address); + + hash = path.hash; + + objc_autoreleasePoolPop(pool); + + return hash; default: @throw [OFInvalidArgumentException exception]; } OFHashFinalize(&hash); @@ -845,5 +902,27 @@ if (address->family != OFSocketAddressFamilyIPX) @throw [OFInvalidArgumentException exception]; memcpy(node, address->sockaddr.ipx.sipx_node, IPX_NODE_LEN); } + +OFString * +OFSocketAddressUNIXPath(const OFSocketAddress *_Nonnull address) +{ + socklen_t length; + + if (address->family != OFSocketAddressFamilyUNIX) + @throw [OFInvalidArgumentException exception]; + + length = address->length - offsetof(struct sockaddr_un, sun_path); + + for (socklen_t i = 0; i < length; i++) + if (address->sockaddr.un.sun_path[i] == 0) + length = i; + + if (length <= 0) + return nil; + + return [OFString stringWithCString: address->sockaddr.un.sun_path + encoding: [OFLocale encoding] + length: length]; +} Index: src/OFStreamSocket.m ================================================================== --- src/OFStreamSocket.m +++ src/OFStreamSocket.m @@ -297,10 +297,15 @@ #endif #ifdef OF_HAVE_IPX case AF_IPX: client->_remoteAddress.family = OFSocketAddressFamilyIPX; break; +#endif +#ifdef OF_HAVE_UNIX_SOCKETS + case AF_UNIX: + client->_remoteAddress.family = OFSocketAddressFamilyUNIX; + break; #endif default: client->_remoteAddress.family = OFSocketAddressFamilyUnknown; break; } Index: src/OFTCPSocket.h ================================================================== --- src/OFTCPSocket.h +++ src/OFTCPSocket.h @@ -144,27 +144,27 @@ * @return The port to use as a SOCKS5 proxy when creating a new socket */ + (uint16_t)SOCKS5Port; /** - * @brief Connect the OFTCPSocket to the specified destination. + * @brief Connects the OFTCPSocket to the specified destination. * * @param host The host to connect to * @param port The port on the host to connect to */ - (void)connectToHost: (OFString *)host port: (uint16_t)port; /** - * @brief Asynchronously connect the OFTCPSocket to the specified destination. + * @brief Asynchronously connects the OFTCPSocket to the specified destination. * * @param host The host to connect to * @param port The port on the host to connect to */ - (void)asyncConnectToHost: (OFString *)host port: (uint16_t)port; /** - * @brief Asynchronously connect the OFTCPSocket to the specified destination. + * @brief Asynchronously connects the OFTCPSocket to the specified destination. * * @param host The host to connect to * @param port The port on the host to connect to * @param runLoopMode The run loop mode in which to perform the async connect */ @@ -172,11 +172,11 @@ port: (uint16_t)port runLoopMode: (OFRunLoopMode)runLoopMode; #ifdef OF_HAVE_BLOCKS /** - * @brief Asynchronously connect the OFTCPSocket to the specified destination. + * @brief Asynchronously connects the OFTCPSocket to the specified destination. * * @param host The host to connect to * @param port The port on the host to connect to * @param block The block to execute once the connection has been established */ @@ -183,11 +183,11 @@ - (void)asyncConnectToHost: (OFString *)host port: (uint16_t)port block: (OFTCPSocketAsyncConnectBlock)block; /** - * @brief Asynchronously connect the OFTCPSocket to the specified destination. + * @brief Asynchronously connects the OFTCPSocket to the specified destination. * * @param host The host to connect to * @param port The port on the host to connect to * @param runLoopMode The run loop mode in which to perform the async connect * @param block The block to execute once the connection has been established @@ -197,11 +197,11 @@ runLoopMode: (OFRunLoopMode)runLoopMode block: (OFTCPSocketAsyncConnectBlock)block; #endif /** - * @brief Bind the socket to the specified host and port. + * @brief Binds the socket to the specified host and port. * * @param host The host to bind to. Use `@"0.0.0.0"` for IPv4 or `@"::"` for * IPv6 to bind to all. * @param port The port to bind to. If the port is 0, an unused port will be * chosen, which can be obtained using the return value. ADDED src/OFUNIXDatagramSocket.h Index: src/OFUNIXDatagramSocket.h ================================================================== --- src/OFUNIXDatagramSocket.h +++ src/OFUNIXDatagramSocket.h @@ -0,0 +1,72 @@ +/* + * Copyright (c) 2008-2021 Jonathan Schleifer + * + * All rights reserved. + * + * This file is part of ObjFW. It may be distributed under the terms of the + * Q Public License 1.0, which can be found in the file LICENSE.QPL included in + * the packaging of this file. + * + * Alternatively, it may be distributed under the terms of the GNU General + * Public License, either version 2 or 3, which can be found in the file + * LICENSE.GPLv2 or LICENSE.GPLv3 respectively included in the packaging of this + * file. + */ + +#import "OFDatagramSocket.h" + +OF_ASSUME_NONNULL_BEGIN + +@class OFString; + +/** + * @protocol OFUNIXDatagramSocketDelegate OFUNIXDatagramSocket.h \ + * ObjFW/OFUNIXDatagramSocket.h + * + * @brief A delegate for OFUNIXDatagramSocket. + */ +@protocol OFUNIXDatagramSocketDelegate +@end + +/** + * @class OFUNIXDatagramSocket OFUNIXDatagramSocket.h \ + * ObjFW/OFUNIXDatagramSocket.h + * + * @brief A class which provides methods to create and use UNIX datagram + * sockets. + * + * Addresses are of type @ref OFSocketAddress. You can use + * @ref OFSocketAddressMakeUNIX to create an address or + * @ref OFSocketAddressUNIXPath to get the socket path. + * + * @warning Even though the OFCopying protocol is implemented, it does *not* + * return an independent copy of the socket, but instead retains it. + * This is so that the socket can be used as a key for a dictionary, + * so context can be associated with a socket. Using a socket in more + * than one thread at the same time is not thread-safe, even if copy + * was called to create one "instance" for every thread! + */ +@interface OFUNIXDatagramSocket: OFDatagramSocket +{ + OF_RESERVE_IVARS(OFUNIXDatagramSocket, 4) +} + +/** + * @brief The delegate for asynchronous operations on the socket. + * + * @note The delegate is retained for as long as asynchronous operations are + * still ongoing. + */ +@property OF_NULLABLE_PROPERTY (assign, nonatomic) + id delegate; + +/** + * @brief Bind the socket to the specified path. + * + * @param path The path to bind to + * @return The address on which this socket can be reached + */ +- (OFSocketAddress)bindToPath: (OFString *)path; +@end + +OF_ASSUME_NONNULL_END ADDED src/OFUNIXDatagramSocket.m Index: src/OFUNIXDatagramSocket.m ================================================================== --- src/OFUNIXDatagramSocket.m +++ src/OFUNIXDatagramSocket.m @@ -0,0 +1,74 @@ +/* + * Copyright (c) 2008-2021 Jonathan Schleifer + * + * All rights reserved. + * + * This file is part of ObjFW. It may be distributed under the terms of the + * Q Public License 1.0, which can be found in the file LICENSE.QPL included in + * the packaging of this file. + * + * Alternatively, it may be distributed under the terms of the GNU General + * Public License, either version 2 or 3, which can be found in the file + * LICENSE.GPLv2 or LICENSE.GPLv3 respectively included in the packaging of this + * file. + */ + +#include "config.h" + +#include + +#ifdef HAVE_FCNTL_H +# include +#endif + +#import "OFUNIXDatagramSocket.h" +#import "OFSocket.h" +#import "OFSocket+Private.h" +#import "OFString.h" + +#import "OFAlreadyConnectedException.h" +#import "OFBindFailedException.h" + +@implementation OFUNIXDatagramSocket +@dynamic delegate; + +- (OFSocketAddress)bindToPath: (OFString *)path +{ + OFSocketAddress address; +#if SOCK_CLOEXEC == 0 && defined(HAVE_FCNTL_H) && defined(FD_CLOEXEC) + int flags; +#endif + + if (_socket != OFInvalidSocketHandle) + @throw [OFAlreadyConnectedException exceptionWithSocket: self]; + + address = OFSocketAddressMakeUNIX(path); + + if ((_socket = socket(address.sockaddr.sockaddr.sa_family, + SOCK_DGRAM | SOCK_CLOEXEC, 0)) == OFInvalidSocketHandle) + @throw [OFBindFailedException + exceptionWithPath: path + socket: self + errNo: OFSocketErrNo()]; + + _canBlock = true; + +#if SOCK_CLOEXEC == 0 && defined(HAVE_FCNTL_H) && defined(FD_CLOEXEC) + if ((flags = fcntl(_socket, F_GETFD, 0)) != -1) + fcntl(_socket, F_SETFD, flags | FD_CLOEXEC); +#endif + + if (bind(_socket, &address.sockaddr.sockaddr, address.length) != 0) { + int errNo = OFSocketErrNo(); + + closesocket(_socket); + _socket = OFInvalidSocketHandle; + + @throw [OFBindFailedException exceptionWithPath: path + socket: self + errNo: errNo]; + } + + return address; +} +@end ADDED src/OFUNIXStreamSocket.h Index: src/OFUNIXStreamSocket.h ================================================================== --- src/OFUNIXStreamSocket.h +++ src/OFUNIXStreamSocket.h @@ -0,0 +1,68 @@ +/* + * Copyright (c) 2008-2021 Jonathan Schleifer + * + * All rights reserved. + * + * This file is part of ObjFW. It may be distributed under the terms of the + * Q Public License 1.0, which can be found in the file LICENSE.QPL included in + * the packaging of this file. + * + * Alternatively, it may be distributed under the terms of the GNU General + * Public License, either version 2 or 3, which can be found in the file + * LICENSE.GPLv2 or LICENSE.GPLv3 respectively included in the packaging of this + * file. + */ + +#import "OFStreamSocket.h" + +OF_ASSUME_NONNULL_BEGIN + +@class OFString; + +/** + * @protocol OFUNIXStreamSocketDelegate OFUNIXStreamSocket.h \ + * ObjFW/OFUNIXStreamSocket.h + * + * A delegate for OFUNIXStreamSocket. + */ +@protocol OFUNIXStreamSocketDelegate +@end + +/** + * @class OFUNIXStreamSocket OFUNIXStreamSocket.h ObjFW/OFUNIXStreamSocket.h + * + * @brief A class which provides methods to create and use UNIX stream sockets. + * + * To connect to a server, create a socket and connect it. + * To create a server, create a socket, bind it and listen on it. + */ +@interface OFUNIXStreamSocket: OFStreamSocket +{ + OF_RESERVE_IVARS(OFUNIXStreamSocket, 4) +} + +/** + * @brief The delegate for asynchronous operations on the socket. + * + * @note The delegate is retained for as long as asynchronous operations are + * still ongoing. + */ +@property OF_NULLABLE_PROPERTY (assign, nonatomic) + id delegate; + +/** + * @brief Connects the OFUNIXStreamSocket to the specified destination. + * + * @param path The path to connect to + */ +- (void)connectToPath: (OFString *)path; + +/** + * @brief Binds the socket to the specified host and port. + * + * @param path The path to bind to + */ +- (void)bindToPath: (OFString *)path; +@end + +OF_ASSUME_NONNULL_END ADDED src/OFUNIXStreamSocket.m Index: src/OFUNIXStreamSocket.m ================================================================== --- src/OFUNIXStreamSocket.m +++ src/OFUNIXStreamSocket.m @@ -0,0 +1,111 @@ +/* + * Copyright (c) 2008-2021 Jonathan Schleifer + * + * All rights reserved. + * + * This file is part of ObjFW. It may be distributed under the terms of the + * Q Public License 1.0, which can be found in the file LICENSE.QPL included in + * the packaging of this file. + * + * Alternatively, it may be distributed under the terms of the GNU General + * Public License, either version 2 or 3, which can be found in the file + * LICENSE.GPLv2 or LICENSE.GPLv3 respectively included in the packaging of this + * file. + */ + +#include "config.h" + +#include + +#ifdef HAVE_FCNTL_H +# include +#endif + +#import "OFUNIXStreamSocket.h" +#import "OFSocket.h" +#import "OFSocket+Private.h" +#import "OFString.h" + +#import "OFAlreadyConnectedException.h" +#import "OFBindFailedException.h" +#import "OFConnectionFailedException.h" + +@implementation OFUNIXStreamSocket +@dynamic delegate; + +- (void)connectToPath: (OFString *)path +{ + OFSocketAddress address; +#if SOCK_CLOEXEC == 0 && defined(HAVE_FCNTL) && defined(FD_CLOEXEC) + int flags; +#endif + + if (_socket != OFInvalidSocketHandle) + @throw [OFAlreadyConnectedException exceptionWithSocket: self]; + + address = OFSocketAddressMakeUNIX(path); + + if ((_socket = socket(address.sockaddr.sockaddr.sa_family, + SOCK_STREAM | SOCK_CLOEXEC, 0)) == OFInvalidSocketHandle) + @throw [OFConnectionFailedException + exceptionWithPath: path + socket: self + errNo: OFSocketErrNo()]; + + _canBlock = true; + +#if SOCK_CLOEXEC == 0 && defined(HAVE_FCNTL) && defined(FD_CLOEXEC) + if ((flags = fcntl(_socket, F_GETFD, 0)) != -1) + fcntl(_socket, F_SETFD, flags | FD_CLOEXEC); +#endif + + if (connect(_socket, &address.sockaddr.sockaddr, address.length) != 0) { + int errNo = OFSocketErrNo(); + + closesocket(_socket); + _socket = OFInvalidSocketHandle; + + @throw [OFConnectionFailedException exceptionWithPath: path + socket: self + errNo: errNo]; + } +} + +- (void)bindToPath: (OFString *)path +{ + OFSocketAddress address; +#if SOCK_CLOEXEC == 0 && defined(HAVE_FCNTL) && defined(FD_CLOEXEC) + int flags; +#endif + + if (_socket != OFInvalidSocketHandle) + @throw [OFAlreadyConnectedException exceptionWithSocket: self]; + + address = OFSocketAddressMakeUNIX(path); + + if ((_socket = socket(address.sockaddr.sockaddr.sa_family, + SOCK_STREAM | SOCK_CLOEXEC, 0)) == OFInvalidSocketHandle) + @throw [OFBindFailedException + exceptionWithPath: path + socket: self + errNo: OFSocketErrNo()]; + + _canBlock = true; + +#if SOCK_CLOEXEC == 0 && defined(HAVE_FCNTL) && defined(FD_CLOEXEC) + if ((flags = fcntl(_socket, F_GETFD, 0)) != -1) + fcntl(_socket, F_SETFD, flags | FD_CLOEXEC); +#endif + + if (bind(_socket, &address.sockaddr.sockaddr, address.length) != 0) { + int errNo = OFSocketErrNo(); + + closesocket(_socket); + _socket = OFInvalidSocketHandle; + + @throw [OFBindFailedException exceptionWithPath: path + socket: self + errNo: errNo]; + } +} +@end Index: src/ObjFW.h ================================================================== --- src/ObjFW.h +++ src/ObjFW.h @@ -82,10 +82,14 @@ # ifdef OF_HAVE_IPX # import "OFIPXSocket.h" # import "OFSPXSocket.h" # import "OFSPXStreamSocket.h" # endif +# ifdef OF_HAVE_UNIX_SOCKETS +# import "OFUNIXDatagramSocket.h" +# import "OFUNIXStreamSocket.h" +# endif #endif #ifdef OF_HAVE_SOCKETS # ifdef OF_HAVE_THREADS # import "OFHTTPClient.h" # endif Index: src/exceptions/OFBindFailedException.h ================================================================== --- src/exceptions/OFBindFailedException.h +++ src/exceptions/OFBindFailedException.h @@ -29,16 +29,18 @@ * * @brief An exception indicating that binding a socket failed. */ @interface OFBindFailedException: OFException { - id _socket; /* IP */ OFString *_host; uint16_t _port; /* IPX */ uint8_t _packetType; + /* UNIX socket */ + OFString *_Nullable _path; + id _socket; int _errNo; } /** * @brief The host on which binding failed. @@ -53,10 +55,15 @@ /** * @brief The IPX packet type for which binding failed. */ @property (readonly, nonatomic) uint8_t packetType; +/** + * @brief The path on which binding failed. + */ +@property OF_NULLABLE_PROPERTY (readonly, nonatomic) OFString *path; + /** * @brief The socket which could not be bound. */ @property (readonly, nonatomic) id socket; @@ -93,10 +100,22 @@ + (instancetype)exceptionWithPort: (uint16_t)port packetType: (uint8_t)packetType socket: (id)socket errNo: (int)errNo; +/** + * @brief Creates a new, autoreleased bind failed exception. + * + * @param path The path on which binding failed + * @param socket The socket which could not be bound + * @param errNo The errno of the error that occurred + * @return A new, autoreleased bind failed exception + */ ++ (instancetype)exceptionWithPath: (OFString *)path + socket: (id)socket + errNo: (int)errNo; + - (instancetype)init OF_UNAVAILABLE; /** * @brief Initializes an already allocated bind failed exception. * @@ -122,8 +141,19 @@ */ - (instancetype)initWithPort: (uint16_t)port packetType: (uint8_t)packetType socket: (id)socket errNo: (int)errNo; +/** + * @brief Initializes an already allocated bind failed exception. + * + * @param path The path on which binding failed + * @param socket The socket which could not be bound + * @param errNo The errno of the error that occurred + * @return An initialized bind failed exception + */ +- (instancetype)initWithPath: (OFString *)path + socket: (id)socket + errNo: (int)errNo; @end OF_ASSUME_NONNULL_END Index: src/exceptions/OFBindFailedException.m ================================================================== --- src/exceptions/OFBindFailedException.m +++ src/exceptions/OFBindFailedException.m @@ -17,11 +17,11 @@ #import "OFBindFailedException.h" #import "OFString.h" @implementation OFBindFailedException -@synthesize host = _host, port = _port, packetType = _packetType; +@synthesize host = _host, port = _port, packetType = _packetType, path = _path; @synthesize socket = _socket, errNo = _errNo; + (instancetype)exception { OF_UNRECOGNIZED_SELECTOR @@ -46,10 +46,19 @@ return [[[self alloc] initWithPort: port packetType: packetType socket: sock errNo: errNo] autorelease]; } + ++ (instancetype)exceptionWithPath: (OFString *)path + socket: (id)sock + errNo: (int)errNo +{ + return [[[self alloc] initWithPath: path + socket: sock + errNo: errNo] autorelease]; +} - (instancetype)init { OF_INVALID_INIT_METHOD } @@ -91,22 +100,45 @@ @throw e; } return self; } + +- (instancetype)initWithPath: (OFString *)path + socket: (id)sock + errNo: (int)errNo +{ + self = [super init]; + + @try { + _path = [path copy]; + _socket = [sock retain]; + _errNo = errNo; + } @catch (id e) { + [self release]; + @throw e; + } + + return self; +} - (void)dealloc { [_host release]; + [_path release]; [_socket release]; [super dealloc]; } - (OFString *)description { - if (_host != nil) + if (_path != nil) + return [OFString stringWithFormat: + @"Binding to path %@ failed in socket of type %@: %@", + _path, [_socket class], OFStrError(_errNo)]; + else if (_host != nil) return [OFString stringWithFormat: @"Binding to port %" @PRIu16 @" on host %@ failed in " @"socket of type %@: %@", _port, _host, [_socket class], OFStrError(_errNo)]; else Index: src/exceptions/OFConnectionFailedException.h ================================================================== --- src/exceptions/OFConnectionFailedException.h +++ src/exceptions/OFConnectionFailedException.h @@ -29,23 +29,19 @@ * * @brief An exception indicating that a connection could not be established. */ @interface OFConnectionFailedException: OFException { - id _socket; OFString *_host; uint16_t _port; unsigned char _node[IPX_NODE_LEN]; uint32_t _network; + OFString *_Nullable _path; + id _socket; int _errNo; } -/** - * @brief The socket which could not connect. - */ -@property (readonly, nonatomic) id socket; - /** * @brief The host to which the connection failed. */ @property OF_NULLABLE_PROPERTY (readonly, nonatomic) OFString *host; @@ -62,10 +58,20 @@ /** * @brief The IPX network of the node to which the connection failed. */ @property (readonly, nonatomic) uint32_t network; +/** + * @brief The path to which the connection failed. + */ +@property OF_NULLABLE_PROPERTY (readonly, nonatomic) OFString *path; + +/** + * @brief The socket which could not connect. + */ +@property (readonly, nonatomic) id socket; + /** * @brief The errno of the error that occurred. */ @property (readonly, nonatomic) int errNo; @@ -99,10 +105,22 @@ network: (uint32_t)network port: (uint16_t)port socket: (id)socket errNo: (int)errNo; +/** + * @brief Creates a new, autoreleased connection failed exception. + * + * @param path The path to which the connection failed + * @param socket The socket which could not connect + * @param errNo The errno of the error that occurred + * @return A new, autoreleased connection failed exception + */ ++ (instancetype)exceptionWithPath: (OFString *)path + socket: (id)socket + errNo: (int)errNo; + - (instancetype)init OF_UNAVAILABLE; /** * @brief Initializes an already allocated connection failed exception. * @@ -130,8 +148,20 @@ - (instancetype)initWithNode: (unsigned char [_Nullable IPX_NODE_LEN])node network: (uint32_t)network port: (uint16_t)port socket: (id)socket errNo: (int)errNo; + +/** + * @brief Initializes an already allocated connection failed exception. + * + * @param path The path to which the connection failed + * @param socket The socket which could not connect + * @param errNo The errno of the error that occurred + * @return An initialized connection failed exception + */ +- (instancetype)initWithPath: (OFString *)path + socket: (id)socket + errNo: (int)errNo; @end OF_ASSUME_NONNULL_END Index: src/exceptions/OFConnectionFailedException.m ================================================================== --- src/exceptions/OFConnectionFailedException.m +++ src/exceptions/OFConnectionFailedException.m @@ -17,12 +17,12 @@ #import "OFConnectionFailedException.h" #import "OFString.h" @implementation OFConnectionFailedException -@synthesize host = _host, port = _port, network = _network, socket = _socket; -@synthesize errNo = _errNo; +@synthesize host = _host, port = _port, network = _network, path = _path; +@synthesize socket = _socket, errNo = _errNo; + (instancetype)exception { OF_UNRECOGNIZED_SELECTOR } @@ -48,10 +48,19 @@ network: network port: port socket: sock errNo: errNo] autorelease]; } + ++ (instancetype)exceptionWithPath: (OFString *)path + socket: (id)sock + errNo: (int)errNo +{ + return [[[self alloc] initWithPath: path + socket: sock + errNo: errNo] autorelease]; +} - (instancetype)init { OF_INVALID_INIT_METHOD } @@ -95,14 +104,33 @@ @throw e; } return self; } + +- (instancetype)initWithPath: (OFString *)path + socket: (id)sock + errNo: (int)errNo +{ + self = [super init]; + + @try { + _path = [path copy]; + _socket = [sock retain]; + _errNo = errNo; + } @catch (id e) { + [self release]; + @throw e; + } + + return self; +} - (void)dealloc { [_host release]; + [_path release]; [_socket release]; [super dealloc]; } @@ -111,11 +139,16 @@ return _node; } - (OFString *)description { - if (_host != nil) + if (_path != nil) + return [OFString stringWithFormat: + @"A connection to %@ could not be established in socket of " + @"type %@: %@", + _path, [_socket class], OFStrError(_errNo)]; + else if (_host != nil) return [OFString stringWithFormat: @"A connection to %@ on port %" @PRIu16 @" could not be " @"established in socket of type %@: %@", _host, _port, [_socket class], OFStrError(_errNo)]; else if (memcmp(_node, "\0\0\0\0\0", IPX_NODE_LEN) == 0) Index: src/objfw-defs.h.in ================================================================== --- src/objfw-defs.h.in +++ src/objfw-defs.h.in @@ -1,8 +1,9 @@ #undef OF_APPLE_RUNTIME #undef OF_BIG_ENDIAN #undef OF_FLOAT_BIG_ENDIAN +#undef OF_HAVE_AFUNIX_H #undef OF_HAVE_ATOMIC_BUILTINS #undef OF_HAVE_ATOMIC_OPS #undef OF_HAVE_BUILTIN_BSWAP16 #undef OF_HAVE_BUILTIN_BSWAP32 #undef OF_HAVE_BUILTIN_BSWAP64 @@ -32,15 +33,17 @@ #undef OF_HAVE_STDNORETURN #undef OF_HAVE_SYMLINK #undef OF_HAVE_SYNC_BUILTINS #undef OF_HAVE_SYS_SOCKET_H #undef OF_HAVE_SYS_TYPES_H +#undef OF_HAVE_SYS_UN_H #undef OF_HAVE_THREADS #undef OF_HAVE_UNICODE_TABLES +#undef OF_HAVE_UNIX_SOCKETS #undef OF_HAVE__THREAD_LOCAL #undef OF_HAVE___THREAD #undef OF_NINTENDO_3DS #undef OF_NINTENDO_DS #undef OF_NO_SHARED #undef OF_OBJFW_RUNTIME #undef OF_UNIVERSAL #undef OF_WII Index: tests/Makefile ================================================================== --- tests/Makefile +++ tests/Makefile @@ -54,23 +54,26 @@ OFSHA1HashTests.m \ OFSHA224HashTests.m \ OFSHA256HashTests.m \ OFSHA384HashTests.m \ OFSHA512HashTests.m -SRCS_IPX = OFIPXSocketTests.m \ - OFSPXSocketTests.m \ - OFSPXStreamSocketTests.m SRCS_PLUGINS = OFPluginTests.m SRCS_SOCKETS = OFDNSResolverTests.m \ ${OF_HTTP_CLIENT_TESTS_M} \ OFHTTPCookieTests.m \ OFHTTPCookieManagerTests.m \ OFKernelEventObserverTests.m \ OFSocketTests.m \ OFTCPSocketTests.m \ OFUDPSocketTests.m \ - ${USE_SRCS_IPX} + ${USE_SRCS_IPX} \ + ${USE_SRCS_UNIX_SOCKETS} +SRCS_IPX = OFIPXSocketTests.m \ + OFSPXSocketTests.m \ + OFSPXStreamSocketTests.m +SRCS_UNIX_SOCKETS = OFUNIXDatagramSocketTests.m \ + OFUNIXStreamSocketTests.m SRCS_THREADS = OFThreadTests.m SRCS_WINDOWS = OFWindowsRegistryKeyTests.m IOS_USER ?= mobile IOS_TMP ?= /tmp/objfw-test Index: tests/OFSPXSocketTests.m ================================================================== --- tests/OFSPXSocketTests.m +++ tests/OFSPXSocketTests.m @@ -70,11 +70,11 @@ @implementation TestsAppDelegate (OFSPXSocketTests) - (void)SPXSocketTests { void *pool = objc_autoreleasePoolPush(); - OFSPXSocket *sockClient, *sockServer, *sockAccepted;; + OFSPXSocket *sockClient, *sockServer, *sockAccepted; OFSocketAddress address1; const OFSocketAddress *address2; unsigned char node[IPX_NODE_LEN], node2[IPX_NODE_LEN]; uint32_t network; uint16_t port; Index: tests/OFSPXStreamSocketTests.m ================================================================== --- tests/OFSPXStreamSocketTests.m +++ tests/OFSPXStreamSocketTests.m @@ -70,11 +70,11 @@ @implementation TestsAppDelegate (OFSPXStreamSocketTests) - (void)SPXStreamSocketTests { void *pool = objc_autoreleasePoolPush(); - OFSPXStreamSocket *sockClient, *sockServer, *sockAccepted;; + OFSPXStreamSocket *sockClient, *sockServer, *sockAccepted; OFSocketAddress address1; const OFSocketAddress *address2; unsigned char node[IPX_NODE_LEN], node2[IPX_NODE_LEN]; uint32_t network; uint16_t port; ADDED tests/OFUNIXDatagramSocketTests.m Index: tests/OFUNIXDatagramSocketTests.m ================================================================== --- tests/OFUNIXDatagramSocketTests.m +++ tests/OFUNIXDatagramSocketTests.m @@ -0,0 +1,83 @@ +/* + * Copyright (c) 2008-2021 Jonathan Schleifer + * + * All rights reserved. + * + * This file is part of ObjFW. It may be distributed under the terms of the + * Q Public License 1.0, which can be found in the file LICENSE.QPL included in + * the packaging of this file. + * + * Alternatively, it may be distributed under the terms of the GNU General + * Public License, either version 2 or 3, which can be found in the file + * LICENSE.GPLv2 or LICENSE.GPLv3 respectively included in the packaging of this + * file. + */ + +#include "config.h" + +#include + +#import "TestsAppDelegate.h" + +static OFString *const module = @"OFUNIXDatagramSocket"; + +@implementation TestsAppDelegate (OFUNIXDatagramSocketTests) +- (void)UNIXDatagramSocketTests +{ + void *pool = objc_autoreleasePoolPush(); + OFString *path; + OFUNIXDatagramSocket *sock; + OFSocketAddress address1, address2; + char buffer[5]; + +#ifdef OF_HAVE_FILES + path = [[OFSystemInfo temporaryDirectoryPath] + stringByAppendingPathComponent: [[OFUUID UUID] UUIDString]]; +#else + /* + * We can have sockets, including UNIX sockets, while file support is + * disabled. + */ + path = [OFString stringWithFormat: @"/tmp/%@", + [[OFUUID UUID] UUIDString]]; +#endif + + TEST(@"+[socket]", (sock = [OFUNIXDatagramSocket socket])) + + @try { + TEST(@"-[bindToPath:]", R(address1 = [sock bindToPath: path])) + } @catch (OFBindFailedException *e) { + if (e.errNo == EAFNOSUPPORT) { + [OFStdOut setForegroundColor: [OFColor lime]]; + [OFStdOut writeLine: + @"\r[OFUNIXDatagramSocket] -[bindToPath:]: " + @"UNIX datagram sockets unsupported, skipping " + @"tests"]; + + objc_autoreleasePoolPop(pool); + return; + } else + @throw e; + } + + @try { + TEST(@"-[sendBuffer:length:receiver:]", + R([sock sendBuffer: "Hello" length: 5 receiver: &address1])) + + TEST(@"-[receiveIntoBuffer:length:sender:]", + [sock receiveIntoBuffer: buffer + length: 5 + sender: &address2] == 5 && + memcmp(buffer, "Hello", 5) == 0 && + OFSocketAddressEqual(&address1, &address2) && + OFSocketAddressHash(&address1) == + OFSocketAddressHash(&address2)) + } @finally { +#ifdef OF_HAVE_FILES + [[OFFileManager defaultManager] removeItemAtPath: path]; +#endif + } + + objc_autoreleasePoolPop(pool); +} +@end ADDED tests/OFUNIXStreamSocketTests.m Index: tests/OFUNIXStreamSocketTests.m ================================================================== --- tests/OFUNIXStreamSocketTests.m +++ tests/OFUNIXStreamSocketTests.m @@ -0,0 +1,87 @@ +/* + * Copyright (c) 2008-2021 Jonathan Schleifer + * + * All rights reserved. + * + * This file is part of ObjFW. It may be distributed under the terms of the + * Q Public License 1.0, which can be found in the file LICENSE.QPL included in + * the packaging of this file. + * + * Alternatively, it may be distributed under the terms of the GNU General + * Public License, either version 2 or 3, which can be found in the file + * LICENSE.GPLv2 or LICENSE.GPLv3 respectively included in the packaging of this + * file. + */ + +#include "config.h" + +#include + +#import "TestsAppDelegate.h" + +static OFString *const module = @"OFUNIXStreamSocket"; + +@implementation TestsAppDelegate (OFUNIXStreamSocketTests) +- (void)UNIXStreamSocketTests +{ + void *pool = objc_autoreleasePoolPush(); + OFString *path; + OFUNIXStreamSocket *sockClient, *sockServer, *sockAccepted; + char buffer[5]; + +#ifdef OF_HAVE_FILES + path = [[OFSystemInfo temporaryDirectoryPath] + stringByAppendingPathComponent: [[OFUUID UUID] UUIDString]]; +#else + /* + * We can have sockets, including UNIX sockets, while file support is + * disabled. + */ + path = [OFString stringWithFormat: @"/tmp/%@", + [[OFUUID UUID] UUIDString]]; +#endif + + TEST(@"+[socket]", (sockClient = [OFUNIXStreamSocket socket]) && + (sockServer = [OFUNIXStreamSocket socket])) + + @try { + TEST(@"-[bindToPath:]", R([sockServer bindToPath: path])) + } @catch (OFBindFailedException *e) { + if (e.errNo == EAFNOSUPPORT) { + [OFStdOut setForegroundColor: [OFColor lime]]; + [OFStdOut writeLine: + @"\r[OFUNIXStreamSocket] -[bindToPath:]: " + @"UNIX stream sockets unsupported, skipping tests"]; + + objc_autoreleasePoolPop(pool); + return; + } else + @throw e; + } + + @try { + TEST(@"-[listen]", R([sockServer listen])) + + TEST(@"-[connectToPath:]", + R([sockClient connectToPath: path])) + + TEST(@"-[accept]", (sockAccepted = [sockServer accept])) + + TEST(@"-[writeBuffer:length:]", + R([sockAccepted writeBuffer: "Hello" length: 5])) + + TEST(@"-[readIntoBuffer:length:]", + [sockClient readIntoBuffer: buffer length: 5] == 5 && + memcmp(buffer, "Hello", 5) == 0) + + TEST(@"-[remoteAddress]", + OFSocketAddressUNIXPath(sockAccepted.remoteAddress) == nil) + } @finally { +#ifdef OF_HAVE_FILES + [[OFFileManager defaultManager] removeItemAtPath: path]; +#endif + } + + objc_autoreleasePoolPop(pool); +} +@end Index: tests/TestsAppDelegate.h ================================================================== --- tests/TestsAppDelegate.h +++ tests/TestsAppDelegate.h @@ -240,10 +240,18 @@ @end @interface TestsAppDelegate (OFUDPSocketTests) - (void)UDPSocketTests; @end + +@interface TestsAppDelegate (OFUNIXDatagramSocketTests) +- (void)UNIXDatagramSocketTests; +@end + +@interface TestsAppDelegate (OFUNIXStreamSocketTests) +- (void)UNIXStreamSocketTests; +@end @interface TestsAppDelegate (OFURLTests) - (void)URLTests; @end Index: tests/TestsAppDelegate.m ================================================================== --- tests/TestsAppDelegate.m +++ tests/TestsAppDelegate.m @@ -353,10 +353,14 @@ # ifdef OF_HAVE_IPX [self IPXSocketTests]; [self SPXSocketTests]; [self SPXStreamSocketTests]; # endif +# ifdef OF_HAVE_UNIX_SOCKETS + [self UNIXDatagramSocketTests]; + [self UNIXStreamSocketTests]; +# endif [self kernelEventObserverTests]; #endif #ifdef OF_HAVE_THREADS [self threadTests]; #endif