From f72b9d5659f12c67b006d4faf838f68935691dc3 Mon Sep 17 00:00:00 2001 From: Adriano dos Santos Fernandes Date: Thu, 21 May 2026 22:03:40 -0300 Subject: [PATCH 1/2] Feature #9033 - Support for Unix Domain Sockets --- builds/cmake/Configure.cmake | 1 + builds/install/misc/firebird.conf | 15 ++ configure.ac | 2 +- doc/Firebird_conf.txt | 1 + doc/README.connection_strings | 11 +- src/common/config/config.h | 5 + src/include/gen/autoconfig.h.in | 3 + src/remote/client/interface.cpp | 55 +++++- src/remote/inet.cpp | 294 +++++++++++++++++++++++++++++- src/remote/remote.h | 2 + 10 files changed, 382 insertions(+), 7 deletions(-) diff --git a/builds/cmake/Configure.cmake b/builds/cmake/Configure.cmake index 9faa0e50eb7..95bc56d0df3 100644 --- a/builds/cmake/Configure.cmake +++ b/builds/cmake/Configure.cmake @@ -179,6 +179,7 @@ set(include_files_list sys/time.h sys/timeb.h sys/types.h + sys/un.h sys/uio.h sys/wait.h termio.h diff --git a/builds/install/misc/firebird.conf b/builds/install/misc/firebird.conf index 4f7daf736ac..e36b366aa14 100644 --- a/builds/install/misc/firebird.conf +++ b/builds/install/misc/firebird.conf @@ -781,6 +781,21 @@ #RemoteServicePort = 3050 +# ---------------------------- +# Unix domain socket path to be used for client database and service +# connections on POSIX platforms. If set, the server listens on this +# socket instead of the TCP service name/port. The name should not +# contain colons. +# +# Event notification connections use a separate Unix domain socket in +# the same directory as this socket. Firebird chooses its name as +# fb_event... +# +# Type: string +# +#RemoteServiceUnixSocket = + + # ---------------------------- # The TCP port number to be used for server Event Notification # messages. The value of 0 (Zero) means that the server will choose diff --git a/configure.ac b/configure.ac index 90ed73c0e97..32ecd9d588d 100644 --- a/configure.ac +++ b/configure.ac @@ -893,7 +893,7 @@ AC_CHECK_HEADERS(iconv.h) AC_CHECK_HEADERS(linux/falloc.h) AC_CHECK_HEADERS(utime.h) -AC_CHECK_HEADERS(socket.h sys/socket.h sys/sockio.h winsock2.h) +AC_CHECK_HEADERS(socket.h sys/socket.h sys/sockio.h sys/un.h winsock2.h) AC_CHECK_DECLS(SOCK_CLOEXEC,,,[[ #ifdef HAVE_SYS_SOCKET_H #include diff --git a/doc/Firebird_conf.txt b/doc/Firebird_conf.txt index 70d635e6342..2f00799c597 100644 --- a/doc/Firebird_conf.txt +++ b/doc/Firebird_conf.txt @@ -142,6 +142,7 @@ DeadThreadsCollection integer default 50 PriorityBoost integer default 5 RemoteServiceName string default gds_db RemoteServicePort integer default 3050 (TCP port number) +RemoteServiceUnixSocket string default empty (Unix domain socket path, POSIX only) IpcName string default "FIREBIRD" (Windows only) MaxUnflushedWrites integer diff --git a/doc/README.connection_strings b/doc/README.connection_strings index 6bc1d1f3fcf..6f24cdf4a0c 100644 --- a/doc/README.connection_strings +++ b/doc/README.connection_strings @@ -110,6 +110,12 @@ Examples: inet://C:\db\mydb.fdb inet://mydb + Local connection via Unix domain socket (POSIX only): + + unix:///run/firebird/firebird.sock:/db/mydb.fdb + unix:///run/firebird/firebird.sock:mydb + unix:///run/firebird/firebird.sock:service_mgr + Local connection via shared memory: xnet://C:\db\mydb.fdb @@ -135,7 +141,10 @@ to connect locally using a specific transport protocol, please specify: inet:// or + unix://: + or xnet:// Note: XNET (shared memory) protocol is available on Windows only. - +Unix domain socket protocol is available on POSIX only and the socket path +must not contain ':'. diff --git a/src/common/config/config.h b/src/common/config/config.h index 3db3227bac8..d2831ded6f3 100644 --- a/src/common/config/config.h +++ b/src/common/config/config.h @@ -136,6 +136,7 @@ enum ConfigKey KEY_DEADLOCK_TIMEOUT, KEY_REMOTE_SERVICE_NAME, KEY_REMOTE_SERVICE_PORT, + KEY_REMOTE_SERVICE_UNIX_SOCKET, KEY_IPC_NAME, KEY_MAX_UNFLUSHED_WRITES, KEY_MAX_UNFLUSHED_WRITE_TIME, @@ -229,6 +230,7 @@ inline constexpr ConfigEntry entries[MAX_CONFIG_KEY] = {TYPE_INTEGER, "DeadlockTimeout", false, 10}, // seconds {TYPE_STRING, "RemoteServiceName", false, FB_SERVICE_NAME}, {TYPE_INTEGER, "RemoteServicePort", false, 0}, + {TYPE_STRING, "RemoteServiceUnixSocket", true, nullptr}, {TYPE_STRING, "IpcName", false, FB_IPC_NAME}, #ifdef WIN_NT {TYPE_INTEGER, "MaxUnflushedWrites", false, 100}, @@ -521,6 +523,9 @@ class Config : public RefCounted, public GlobalStorage // Service port for INET CONFIG_GET_PER_DB_KEY(unsigned short, getRemoteServicePort, KEY_REMOTE_SERVICE_PORT, getInt); + // Unix domain socket for remote protocol + CONFIG_GET_GLOBAL_STR(getRemoteServiceUnixSocket, KEY_REMOTE_SERVICE_UNIX_SOCKET); + // Name for IPC-related objects CONFIG_GET_PER_DB_STR(getIpcName, KEY_IPC_NAME); diff --git a/src/include/gen/autoconfig.h.in b/src/include/gen/autoconfig.h.in index ccc3c22e212..f95da912d68 100644 --- a/src/include/gen/autoconfig.h.in +++ b/src/include/gen/autoconfig.h.in @@ -351,6 +351,9 @@ /* Define to 1 if you have the header file. */ #cmakedefine HAVE_SYS_TYPES_H 1 +/* Define to 1 if you have the header file. */ +#cmakedefine HAVE_SYS_UN_H 1 + /* Define to 1 if you have the header file. */ #cmakedefine HAVE_SYS_UIO_H 1 diff --git a/src/remote/client/interface.cpp b/src/remote/client/interface.cpp index 6c961feb63f..3dfe281e2f9 100644 --- a/src/remote/client/interface.cpp +++ b/src/remote/client/interface.cpp @@ -79,6 +79,13 @@ #include #endif +#if !defined(WIN_NT) +#include +#ifdef HAVE_SYS_UN_H +#include +#endif +#endif + #ifdef WIN_NT #include #endif @@ -93,6 +100,10 @@ const char* const PROTOCOL_INET = "inet"; const char* const PROTOCOL_INET4 = "inet4"; const char* const PROTOCOL_INET6 = "inet6"; +#if !defined(WIN_NT) && defined(HAVE_SYS_UN_H) +const char* const PROTOCOL_UNIX = "unix"; +#endif + #ifdef WIN_NT const char* const PROTOCOL_XNET = "xnet"; #endif @@ -1167,6 +1178,36 @@ static void authReceiveResponse(bool havePacket, ClntAuthBlock& authItr, rem_por static AtomicCounter remote_event_id; +#if !defined(WIN_NT) && defined(HAVE_SYS_UN_H) +static bool analyzeUnixProtocol(PathName& expandedName, PathName& nodeName) +{ + nodeName.erase(); + + const PathName prefix = PathName(PROTOCOL_UNIX) + "://"; + + if (prefix.length() > expandedName.length()) + return false; + + if (IgnoreCaseComparator::compare(prefix.c_str(), expandedName.c_str(), prefix.length()) != 0) + return false; + + PathName savedName = expandedName; + expandedName.erase(0, prefix.length()); + + const PathName::size_type separator = expandedName.find(':'); + if (separator == PathName::npos || separator == 0 || separator == expandedName.length() - 1) + { + expandedName = savedName; + return false; + } + + nodeName = expandedName.substr(0, separator); + expandedName.erase(0, separator + 1); + + return true; +} +#endif + static constexpr unsigned ANALYZE_USER_VFY = 0x01; static constexpr unsigned ANALYZE_LOOPBACK = 0x02; static constexpr unsigned ANALYZE_MOUNTS = 0x04; @@ -7921,14 +7962,24 @@ static rem_port* analyze(ClntAuthBlock& cBlock, PathName& attach_name, unsigned else #endif +#if !defined(WIN_NT) && defined(HAVE_SYS_UN_H) + if (analyzeUnixProtocol(attach_name, node_name)) + { + ISC_utf8ToSystem(node_name); + port = INET_analyze(&cBlock, attach_name, node_name.c_str(), flags & ANALYZE_USER_VFY, pb, + cBlock.getConfig(), ref_db_name, cryptCb, AF_UNIX); + } + else +#endif + if (ISC_analyze_protocol(PROTOCOL_INET4, attach_name, node_name, INET_SEPARATOR, needFile)) inet_af = AF_INET; else if (ISC_analyze_protocol(PROTOCOL_INET6, attach_name, node_name, INET_SEPARATOR, needFile)) inet_af = AF_INET6; - if (inet_af != AF_UNSPEC || + if (!port && (inet_af != AF_UNSPEC || ISC_analyze_protocol(PROTOCOL_INET, attach_name, node_name, INET_SEPARATOR, needFile) || - ISC_analyze_tcp(attach_name, node_name, needFile)) + ISC_analyze_tcp(attach_name, node_name, needFile))) { if (node_name.isEmpty()) node_name = INET_LOCALHOST; diff --git a/src/remote/inet.cpp b/src/remote/inet.cpp index ae8be9fd84e..99cfe72fa63 100644 --- a/src/remote/inet.cpp +++ b/src/remote/inet.cpp @@ -48,6 +48,7 @@ #include #include #include +#include #include "../common/file_params.h" #include @@ -87,6 +88,10 @@ #include #include #include +#include +#ifdef HAVE_SYS_UN_H +#include +#endif #include #if defined(HAVE_POLL_H) @@ -521,6 +526,15 @@ static void disconnect(rem_port*); static void force_close(rem_port*); static int cleanup_ports(const int, const int, void*); +#if !defined(WIN_NT) && defined(HAVE_SYS_UN_H) +static string makeAuxUnixSocketPath(const string& mainSocketPath); +static void makeUnixSocketAddress(bool releasePort, rem_port* port, const char* path, + sockaddr_un* address, socklen_t* length); +static void removeUnixSocketPath(const string& path); +static rem_port* unix_connect(rem_port* port, const TEXT* socketPath, PACKET* packet, USHORT flag); +static rem_port* unix_listener_socket(rem_port* port, USHORT flag, const TEXT* socketPath); +#endif + #ifdef NO_FORK static int fork(); #endif @@ -624,6 +638,51 @@ static GlobalPtr port_mutex; static GlobalPtr inet_ports; static GlobalPtr ports_to_close; +#if !defined(WIN_NT) && defined(HAVE_SYS_UN_H) +static AtomicCounter unixAuxSequence; + +static void makeUnixSocketAddress(bool releasePort, rem_port* port, const char* path, + sockaddr_un* address, socklen_t* length) +{ + const size_t pathLength = path ? strlen(path) : 0; + + if (!pathLength || pathLength >= sizeof(address->sun_path)) + { + gds__log("INET/unix: invalid Unix socket path length: %s", path ? path : ""); + inet_gen_error(releasePort, port, + Arg::Gds(isc_net_lookup_err) << Arg::Gds(isc_host_unknown)); + } + + memset(address, 0, sizeof(*address)); + address->sun_family = AF_UNIX; + memcpy(address->sun_path, path, pathLength + 1); + *length = offsetof(sockaddr_un, sun_path) + pathLength + 1; +} + +static void removeUnixSocketPath(const string& path) +{ + if (path.isEmpty()) + return; + + struct stat st; + if (lstat(path.c_str(), &st) == 0 && S_ISSOCK(st.st_mode)) + unlink(path.c_str()); +} + +static string makeAuxUnixSocketPath(const string& mainSocketPath) +{ + const FB_SIZE_T slash = mainSocketPath.rfind('/'); + string directory = slash == string::npos ? "." : mainSocketPath.substr(0, slash); + + string path; + path.printf("%s/fb_event.%d.%ld", directory.c_str(), getpid(), + static_cast(unixAuxSequence.exchangeAdd(1))); + + return path; +} + +#endif // !WIN_NT && HAVE_SYS_UN_H + rem_port* INET_analyze(ClntAuthBlock* cBlock, const PathName& file_name, @@ -908,9 +967,20 @@ rem_port* INET_connect(const TEXT* name, if ((!name || !name[0]) && !packet) { +#if !defined(WIN_NT) && defined(HAVE_SYS_UN_H) + const char* const socketPath = port->getPortConfig()->getRemoteServiceUnixSocket(); + if (socketPath && socketPath[0]) + return unix_connect(port, socketPath, packet, flag); +#endif + name = port->getPortConfig()->getRemoteBindAddress(); } +#if !defined(WIN_NT) && defined(HAVE_SYS_UN_H) + if (af == AF_UNIX) + return unix_connect(port, name, packet, flag); +#endif + if (name) { host = name; @@ -1062,6 +1132,108 @@ rem_port* INET_connect(const TEXT* name, return port; } + +#if !defined(WIN_NT) && defined(HAVE_SYS_UN_H) + +static rem_port* unix_connect(rem_port* port, const TEXT* socketPath, PACKET* packet, USHORT flag) +{ + sockaddr_un address; + socklen_t addressLength; + makeUnixSocketAddress(true, port, socketPath, &address, &addressLength); + + port->port_flags |= PORT_unix; + port->port_address = socketPath; + port->port_protocol_id = "UNIX"; + + delete port->port_connection; + port->port_connection = REMOTE_make_string(socketPath); + delete port->port_version; + port->port_version = REMOTE_make_string("unix"); + + port->port_handle = os_utils::socket(AF_UNIX, SOCK_STREAM, 0); + if (port->port_handle == INVALID_SOCKET) + { + inet_error(true, port, packet ? "socket" : "listen", packet ? isc_net_connect_err : + isc_net_connect_listen_err, INET_ERRNO); + } + + if (!packet) + return unix_listener_socket(port, flag, socketPath); + + const int n = connect(port->port_handle, reinterpret_cast(&address), addressLength); + if (n != -1) + { + port->port_peer_name = socketPath; + if (send_full(port, packet)) + return port; + } + + SOCLOSE(port->port_handle); + port->port_handle = INVALID_SOCKET; + inet_error(true, port, "connect", isc_net_connect_err, INET_ERRNO); + + return port; +} + +static rem_port* unix_listener_socket(rem_port* port, USHORT flag, const TEXT* socketPath) +{ + sockaddr_un address; + socklen_t addressLength; + makeUnixSocketAddress(true, port, socketPath, &address, &addressLength); + + removeUnixSocketPath(socketPath); + + if (bind(port->port_handle, reinterpret_cast(&address), addressLength) < 0) + inet_error(true, port, "bind", isc_net_connect_listen_err, INET_ERRNO); + + port->port_flags |= PORT_unix_unlink; + + if (listen(port->port_handle, SOMAXCONN) < 0) + inet_error(false, port, "listen", isc_net_connect_listen_err, INET_ERRNO); + + inet_ports->registerPort(port); + + if (flag & SRVR_multi_client) + { + port->port_dummy_packet_interval = 0; + port->port_dummy_timeout = 0; + port->port_server_flags |= (SRVR_server | SRVR_multi_client); + return port; + } + + while (true) + { + SOCKET s = os_utils::accept(port->port_handle, NULL, NULL); + const int inetErrNo = INET_ERRNO; + if (s == INVALID_SOCKET) + { + if (INET_shutting_down) + return NULL; + inet_error(true, port, "accept", isc_net_connect_err, inetErrNo); + } + + if ((flag & SRVR_debug) || !fork()) + { + SOCLOSE(port->port_handle); + port->port_handle = s; + port->port_server_flags |= SRVR_server; + port->port_flags |= PORT_server; + port->port_flags &= ~PORT_unix_unlink; + return port; + } + + MutexLockGuard guard(waitThreadMutex, FB_FUNCTION); + + if (!procCount++) + Thread::start(waitThread, 0, THREAD_medium); + + SOCLOSE(s); + } +} + +#endif // !WIN_NT && HAVE_SYS_UN_H + + static rem_port* listener_socket(rem_port* port, USHORT flag, const addrinfo* pai) { /************************************** @@ -1534,7 +1706,16 @@ static rem_port* aux_connect(rem_port* port, PACKET* packet) port->port_handle = n; port->port_flags |= PORT_async; - get_peer_info(port); + if (port->port_flags & PORT_unix) + { +#if !defined(WIN_NT) && defined(HAVE_SYS_UN_H) + removeUnixSocketPath(port->port_address); +#endif + port->port_protocol_id = "UNIX"; + port->port_flags &= ~PORT_unix_unlink; + } + else + get_peer_info(port); return port; } @@ -1546,6 +1727,44 @@ static rem_port* aux_connect(rem_port* port, PACKET* packet) new_port->port_dummy_timeout = new_port->port_dummy_packet_interval; P_RESP* response = &packet->p_resp; +#if !defined(WIN_NT) && defined(HAVE_SYS_UN_H) + if (port->port_flags & PORT_unix) + { + string auxPath; + auxPath.assign(reinterpret_cast(response->p_resp_data.cstr_address), + response->p_resp_data.cstr_length); + + sockaddr_un address; + socklen_t addressLength; + makeUnixSocketAddress(false, port, auxPath.c_str(), &address, &addressLength); + + SOCKET n = os_utils::socket(AF_UNIX, SOCK_STREAM, 0); + if (n == INVALID_SOCKET) + { + const int savedError = INET_ERRNO; + port->auxAcceptError(packet); + inet_error(false, port, "socket", isc_net_event_connect_err, savedError); + } + + const int status = connect(n, reinterpret_cast(&address), addressLength); + if (status < 0) + { + const int savedError = INET_ERRNO; + SOCLOSE(n); + port->auxAcceptError(packet); + inet_error(false, port, "connect", isc_net_event_connect_err, savedError); + } + + new_port->port_handle = n; + new_port->port_flags |= PORT_unix; + new_port->port_protocol_id = "UNIX"; + new_port->port_peer_name = port->port_peer_name; + new_port->port_address = auxPath; + + return new_port; + } +#endif + // NJK - Determine address and port to use. // // The address returned by the server may be incorrect if it is behind a NAT box @@ -1610,6 +1829,56 @@ static rem_port* aux_request( rem_port* port, PACKET* packet) * **************************************/ +#if !defined(WIN_NT) && defined(HAVE_SYS_UN_H) + if (port->port_flags & PORT_unix) + { + const string auxPath = makeAuxUnixSocketPath(port->port_address); + + sockaddr_un address; + socklen_t addressLength; + makeUnixSocketAddress(false, port, auxPath.c_str(), &address, &addressLength); + + SOCKET n = os_utils::socket(AF_UNIX, SOCK_STREAM, 0); + if (n == INVALID_SOCKET) + inet_error(false, port, "socket", isc_net_event_listen_err, INET_ERRNO); + + removeUnixSocketPath(auxPath); + + if (bind(n, reinterpret_cast(&address), addressLength) < 0) + { + const int savedError = INET_ERRNO; + SOCLOSE(n); + inet_error(false, port, "bind", isc_net_event_listen_err, savedError); + } + + if (listen(n, 1) < 0) + { + const int savedError = INET_ERRNO; + removeUnixSocketPath(auxPath); + SOCLOSE(n); + inet_error(false, port, "listen", isc_net_event_listen_err, savedError); + } + + rem_port* const new_port = alloc_port(port->port_parent, + (port->port_flags & PORT_no_oob) | PORT_async | PORT_connecting | PORT_unix | PORT_unix_unlink); + port->port_async = new_port; + new_port->port_dummy_packet_interval = port->port_dummy_packet_interval; + new_port->port_dummy_timeout = new_port->port_dummy_packet_interval; + + new_port->port_server_flags = port->port_server_flags; + new_port->port_channel = n; + new_port->port_peer_name = port->port_peer_name; + new_port->port_address = auxPath; + new_port->port_protocol_id = "UNIX"; + + P_RESP* response = &packet->p_resp; + response->p_resp_data.cstr_length = (ULONG) auxPath.length(); + memcpy(response->p_resp_data.cstr_address, auxPath.c_str(), auxPath.length()); + + return new_port; + } +#endif + // listen on (local) address of the original socket SockAddr our_address; if (our_address.getsockname(port->port_handle) < 0) @@ -1771,7 +2040,13 @@ static void disconnect(rem_port* port) return; port->port_state = rem_port::DISCONNECTED; - port->port_flags &= ~PORT_connecting; + +#if !defined(WIN_NT) && defined(HAVE_SYS_UN_H) + if ((port->port_flags & PORT_unix_unlink) && (port->port_flags & PORT_unix)) + removeUnixSocketPath(port->port_address); +#endif + + port->port_flags &= ~(PORT_connecting | PORT_unix_unlink); if (port->port_async) { @@ -2174,7 +2449,14 @@ static rem_port* select_accept( rem_port* main_port) inet_error(true, port, "accept", isc_net_connect_err, INET_ERRNO); } - setKeepAlive(port->port_handle); + if (main_port->port_flags & PORT_unix) + { + port->port_flags |= PORT_unix; + port->port_protocol_id = "UNIX"; + port->port_address = main_port->port_address; + } + else + setKeepAlive(port->port_handle); port->port_flags |= PORT_server; @@ -2515,6 +2797,12 @@ void get_peer_info(rem_port* port) * Port just connected. Obtain some info about connection and peer. * **************************************/ + if (port->port_flags & PORT_unix) + { + port->port_protocol_id = "UNIX"; + return; + } + port->port_protocol_id = "TCPv4"; SockAddr address; diff --git a/src/remote/remote.h b/src/remote/remote.h index f0aaae273af..9e5b282ec02 100644 --- a/src/remote/remote.h +++ b/src/remote/remote.h @@ -1258,6 +1258,8 @@ inline constexpr USHORT PORT_connecting = 0x0400; // Aux connection waits for a //inline constexpr USHORT PORT_z_data = 0x0800; // Zlib incoming buffer has data left after decompression inline constexpr USHORT PORT_compressed = 0x1000; // Compress outgoing stream (does not affect incoming) inline constexpr USHORT PORT_released = 0x2000; // release(), complementary to the first addRef() in constructor, was called +inline constexpr USHORT PORT_unix = 0x4000; // Port uses Unix domain socket transport +inline constexpr USHORT PORT_unix_unlink = 0x8000; // Unlink port_address when Unix domain socket port is disconnected // forward decl class RemotePortGuard; From dfe45ea3f35467a4560d51e26cc15bbd4804b278 Mon Sep 17 00:00:00 2001 From: Adriano dos Santos Fernandes Date: Fri, 22 May 2026 22:32:38 -0300 Subject: [PATCH 2/2] Windows support for Unix Domain Sockets --- builds/cmake/Configure.cmake | 1 + builds/install/misc/firebird.conf | 6 +- configure.ac | 2 +- doc/Firebird_conf.txt | 2 +- doc/README.connection_strings | 8 +- src/include/gen/autoconfig.h.in | 3 + src/include/gen/autoconfig_msvc.h | 1 + src/remote/client/interface.cpp | 23 ++- src/remote/inet.cpp | 183 +++++++++++++++++++----- src/remote/inet_proto.h | 2 +- src/remote/server/os/win32/srvr_w32.cpp | 13 +- 11 files changed, 193 insertions(+), 51 deletions(-) diff --git a/builds/cmake/Configure.cmake b/builds/cmake/Configure.cmake index 95bc56d0df3..3327b0e3647 100644 --- a/builds/cmake/Configure.cmake +++ b/builds/cmake/Configure.cmake @@ -116,6 +116,7 @@ set(CASE_SENSITIVITY "true") set(SUPPORT_RAW_DEVICES 1) set(include_files_list + afunix.h aio.h assert.h atomic.h diff --git a/builds/install/misc/firebird.conf b/builds/install/misc/firebird.conf index e36b366aa14..59142044eb1 100644 --- a/builds/install/misc/firebird.conf +++ b/builds/install/misc/firebird.conf @@ -783,9 +783,9 @@ # ---------------------------- # Unix domain socket path to be used for client database and service -# connections on POSIX platforms. If set, the server listens on this -# socket instead of the TCP service name/port. The name should not -# contain colons. +# connections on platforms with Unix domain socket support. If set, +# the server listens on this socket instead of the TCP service name/port. +# The name should not contain colons, except for a Windows drive letter. # # Event notification connections use a separate Unix domain socket in # the same directory as this socket. Firebird chooses its name as diff --git a/configure.ac b/configure.ac index 32ecd9d588d..de2dd4df9df 100644 --- a/configure.ac +++ b/configure.ac @@ -893,7 +893,7 @@ AC_CHECK_HEADERS(iconv.h) AC_CHECK_HEADERS(linux/falloc.h) AC_CHECK_HEADERS(utime.h) -AC_CHECK_HEADERS(socket.h sys/socket.h sys/sockio.h sys/un.h winsock2.h) +AC_CHECK_HEADERS(afunix.h socket.h sys/socket.h sys/sockio.h sys/un.h winsock2.h) AC_CHECK_DECLS(SOCK_CLOEXEC,,,[[ #ifdef HAVE_SYS_SOCKET_H #include diff --git a/doc/Firebird_conf.txt b/doc/Firebird_conf.txt index 2f00799c597..89a24d888b9 100644 --- a/doc/Firebird_conf.txt +++ b/doc/Firebird_conf.txt @@ -142,7 +142,7 @@ DeadThreadsCollection integer default 50 PriorityBoost integer default 5 RemoteServiceName string default gds_db RemoteServicePort integer default 3050 (TCP port number) -RemoteServiceUnixSocket string default empty (Unix domain socket path, POSIX only) +RemoteServiceUnixSocket string default empty (Unix domain socket path) IpcName string default "FIREBIRD" (Windows only) MaxUnflushedWrites integer diff --git a/doc/README.connection_strings b/doc/README.connection_strings index 6f24cdf4a0c..e3846a89d40 100644 --- a/doc/README.connection_strings +++ b/doc/README.connection_strings @@ -110,11 +110,12 @@ Examples: inet://C:\db\mydb.fdb inet://mydb - Local connection via Unix domain socket (POSIX only): + Local connection via Unix domain socket: unix:///run/firebird/firebird.sock:/db/mydb.fdb unix:///run/firebird/firebird.sock:mydb unix:///run/firebird/firebird.sock:service_mgr + unix://C:\firebird\firebird.sock:C:\db\mydb.fdb Local connection via shared memory: @@ -146,5 +147,6 @@ to connect locally using a specific transport protocol, please specify: xnet:// Note: XNET (shared memory) protocol is available on Windows only. -Unix domain socket protocol is available on POSIX only and the socket path -must not contain ':'. +Unix domain socket protocol is available on platforms with AF_UNIX support. +The socket path must not contain ':' except for a Windows drive letter, as in +unix://C:\firebird\firebird.sock:C:\db\mydb.fdb. diff --git a/src/include/gen/autoconfig.h.in b/src/include/gen/autoconfig.h.in index f95da912d68..1305a9f3971 100644 --- a/src/include/gen/autoconfig.h.in +++ b/src/include/gen/autoconfig.h.in @@ -330,6 +330,9 @@ /* Define to 1 if you have the header file. */ #cmakedefine HAVE_SYS_SIGNAL_H 1 +/* Define to 1 if you have the header file. */ +#cmakedefine HAVE_AFUNIX_H 1 + /* Define to 1 if you have the header file. */ #cmakedefine HAVE_SYS_SOCKET_H 1 diff --git a/src/include/gen/autoconfig_msvc.h b/src/include/gen/autoconfig_msvc.h index 37207739681..c4230cf923f 100644 --- a/src/include/gen/autoconfig_msvc.h +++ b/src/include/gen/autoconfig_msvc.h @@ -92,6 +92,7 @@ #endif /* Headers */ +#define HAVE_AFUNIX_H #define HAVE_ASSERT_H #define HAVE_CTYPE_H #undef HAVE_UNISTD_H diff --git a/src/remote/client/interface.cpp b/src/remote/client/interface.cpp index 3dfe281e2f9..1394caf1236 100644 --- a/src/remote/client/interface.cpp +++ b/src/remote/client/interface.cpp @@ -87,9 +87,14 @@ #endif #ifdef WIN_NT +#include #include #endif +#if (defined(WIN_NT) && defined(HAVE_AFUNIX_H)) || defined(HAVE_SYS_UN_H) +#define HAVE_AF_UNIX_SUPPORT +#endif + #if defined(WIN_NT) #include "../common/isc_proto.h" #include "../remote/os/win32/xnet_proto.h" @@ -100,7 +105,7 @@ const char* const PROTOCOL_INET = "inet"; const char* const PROTOCOL_INET4 = "inet4"; const char* const PROTOCOL_INET6 = "inet6"; -#if !defined(WIN_NT) && defined(HAVE_SYS_UN_H) +#ifdef HAVE_AF_UNIX_SUPPORT const char* const PROTOCOL_UNIX = "unix"; #endif @@ -1178,7 +1183,7 @@ static void authReceiveResponse(bool havePacket, ClntAuthBlock& authItr, rem_por static AtomicCounter remote_event_id; -#if !defined(WIN_NT) && defined(HAVE_SYS_UN_H) +#ifdef HAVE_AF_UNIX_SUPPORT static bool analyzeUnixProtocol(PathName& expandedName, PathName& nodeName) { nodeName.erase(); @@ -1194,7 +1199,17 @@ static bool analyzeUnixProtocol(PathName& expandedName, PathName& nodeName) PathName savedName = expandedName; expandedName.erase(0, prefix.length()); - const PathName::size_type separator = expandedName.find(':'); + PathName::size_type separator = expandedName.find(':'); + +#ifdef WIN_NT + if (separator == 1) + { + const char driveLetter = expandedName[0]; + if ((driveLetter >= 'A' && driveLetter <= 'Z') || (driveLetter >= 'a' && driveLetter <= 'z')) + separator = expandedName.find(':', separator + 1); + } +#endif + if (separator == PathName::npos || separator == 0 || separator == expandedName.length() - 1) { expandedName = savedName; @@ -7962,7 +7977,7 @@ static rem_port* analyze(ClntAuthBlock& cBlock, PathName& attach_name, unsigned else #endif -#if !defined(WIN_NT) && defined(HAVE_SYS_UN_H) +#ifdef HAVE_AF_UNIX_SUPPORT if (analyzeUnixProtocol(attach_name, node_name)) { ISC_utf8ToSystem(node_name); diff --git a/src/remote/inet.cpp b/src/remote/inet.cpp index 99cfe72fa63..581c9930fdc 100644 --- a/src/remote/inet.cpp +++ b/src/remote/inet.cpp @@ -81,6 +81,10 @@ #ifdef WIN_NT #define FD_SETSIZE 2048 +#include +#include +#include +#include #endif #ifndef WIN_NT @@ -102,6 +106,18 @@ #endif // !WIN_NT +#if (defined(WIN_NT) && defined(HAVE_AFUNIX_H)) || defined(HAVE_SYS_UN_H) +#define HAVE_AF_UNIX_SUPPORT +#endif + +#ifdef HAVE_AF_UNIX_SUPPORT +#if defined(WIN_NT) +using UnixSocketAddress = SOCKADDR_UN; +#else +using UnixSocketAddress = sockaddr_un; +#endif +#endif + constexpr int INET_RETRY_CALL = 5; #include "../remote/remote.h" @@ -526,10 +542,10 @@ static void disconnect(rem_port*); static void force_close(rem_port*); static int cleanup_ports(const int, const int, void*); -#if !defined(WIN_NT) && defined(HAVE_SYS_UN_H) +#ifdef HAVE_AF_UNIX_SUPPORT static string makeAuxUnixSocketPath(const string& mainSocketPath); static void makeUnixSocketAddress(bool releasePort, rem_port* port, const char* path, - sockaddr_un* address, socklen_t* length); + UnixSocketAddress* address, socklen_t* length); static void removeUnixSocketPath(const string& path); static rem_port* unix_connect(rem_port* port, const TEXT* socketPath, PACKET* packet, USHORT flag); static rem_port* unix_listener_socket(rem_port* port, USHORT flag, const TEXT* socketPath); @@ -539,18 +555,38 @@ static rem_port* unix_listener_socket(rem_port* port, USHORT flag, const TEXT* s static int fork(); #endif +namespace +{ + struct ForkSocket + { + ForkSocket() + : socket(0), unixSocket(false) + { + } + + ForkSocket(SOCKET aSocket, bool aUnixSocket) + : socket(aSocket), unixSocket(aUnixSocket) + { + } + + SOCKET socket; + bool unixSocket; + }; +} + typedef Array SocketsArray; +typedef Array ForkSocketsArray; #ifdef WIN_NT static int wsaExitHandler(const int, const int, void*); -static int fork(SOCKET, USHORT); +static int fork(SOCKET, USHORT, bool); static THREAD_ENTRY_DECLARE forkThread(THREAD_ENTRY_PARAM); static GlobalPtr forkMutex; static HANDLE forkEvent = INVALID_HANDLE_VALUE; static bool forkThreadStarted = false; -static SocketsArray* forkSockets; +static ForkSocketsArray* forkSockets; #endif static void get_peer_info(rem_port*); @@ -638,11 +674,11 @@ static GlobalPtr port_mutex; static GlobalPtr inet_ports; static GlobalPtr ports_to_close; -#if !defined(WIN_NT) && defined(HAVE_SYS_UN_H) +#ifdef HAVE_AF_UNIX_SUPPORT static AtomicCounter unixAuxSequence; static void makeUnixSocketAddress(bool releasePort, rem_port* port, const char* path, - sockaddr_un* address, socklen_t* length) + UnixSocketAddress* address, socklen_t* length) { const size_t pathLength = path ? strlen(path) : 0; @@ -656,32 +692,70 @@ static void makeUnixSocketAddress(bool releasePort, rem_port* port, const char* memset(address, 0, sizeof(*address)); address->sun_family = AF_UNIX; memcpy(address->sun_path, path, pathLength + 1); - *length = offsetof(sockaddr_un, sun_path) + pathLength + 1; + *length = offsetof(UnixSocketAddress, sun_path) + pathLength + 1; } +#ifdef WIN_NT +static bool isUnixSocketPath(const string& path) +{ + const HANDLE handle = CreateFileA(path.c_str(), FILE_READ_ATTRIBUTES, + FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE, nullptr, OPEN_EXISTING, + FILE_FLAG_OPEN_REPARSE_POINT | FILE_FLAG_BACKUP_SEMANTICS, nullptr); + + if (handle == INVALID_HANDLE_VALUE) + return false; + + FILE_ATTRIBUTE_TAG_INFO info; + const bool result = GetFileInformationByHandleEx(handle, FileAttributeTagInfo, + &info, sizeof(info)) && + (info.FileAttributes & FILE_ATTRIBUTE_REPARSE_POINT) && + info.ReparseTag == IO_REPARSE_TAG_AF_UNIX; + + CloseHandle(handle); + + return result; +} +#endif + static void removeUnixSocketPath(const string& path) { if (path.isEmpty()) return; +#ifdef WIN_NT + if (isUnixSocketPath(path)) + DeleteFileA(path.c_str()); +#else struct stat st; if (lstat(path.c_str(), &st) == 0 && S_ISSOCK(st.st_mode)) unlink(path.c_str()); +#endif } static string makeAuxUnixSocketPath(const string& mainSocketPath) { +#ifdef WIN_NT + const FB_SIZE_T slash = mainSocketPath.find_last_of("\\/"); + const char* const separator = (slash != string::npos && mainSocketPath[slash] == '/') ? "/" : "\\"; +#else const FB_SIZE_T slash = mainSocketPath.rfind('/'); + const char* const separator = "/"; +#endif string directory = slash == string::npos ? "." : mainSocketPath.substr(0, slash); string path; - path.printf("%s/fb_event.%d.%ld", directory.c_str(), getpid(), + path.printf("%s%sfb_event.%lu.%ld", directory.c_str(), separator, +#ifdef WIN_NT + static_cast(GetCurrentProcessId()), +#else + static_cast(getpid()), +#endif static_cast(unixAuxSequence.exchangeAdd(1))); return path; } -#endif // !WIN_NT && HAVE_SYS_UN_H +#endif // HAVE_AF_UNIX_SUPPORT rem_port* INET_analyze(ClntAuthBlock* cBlock, @@ -967,7 +1041,7 @@ rem_port* INET_connect(const TEXT* name, if ((!name || !name[0]) && !packet) { -#if !defined(WIN_NT) && defined(HAVE_SYS_UN_H) +#ifdef HAVE_AF_UNIX_SUPPORT const char* const socketPath = port->getPortConfig()->getRemoteServiceUnixSocket(); if (socketPath && socketPath[0]) return unix_connect(port, socketPath, packet, flag); @@ -976,7 +1050,7 @@ rem_port* INET_connect(const TEXT* name, name = port->getPortConfig()->getRemoteBindAddress(); } -#if !defined(WIN_NT) && defined(HAVE_SYS_UN_H) +#ifdef HAVE_AF_UNIX_SUPPORT if (af == AF_UNIX) return unix_connect(port, name, packet, flag); #endif @@ -1133,11 +1207,11 @@ rem_port* INET_connect(const TEXT* name, } -#if !defined(WIN_NT) && defined(HAVE_SYS_UN_H) +#ifdef HAVE_AF_UNIX_SUPPORT static rem_port* unix_connect(rem_port* port, const TEXT* socketPath, PACKET* packet, USHORT flag) { - sockaddr_un address; + UnixSocketAddress address; socklen_t addressLength; makeUnixSocketAddress(true, port, socketPath, &address, &addressLength); @@ -1177,7 +1251,7 @@ static rem_port* unix_connect(rem_port* port, const TEXT* socketPath, PACKET* pa static rem_port* unix_listener_socket(rem_port* port, USHORT flag, const TEXT* socketPath) { - sockaddr_un address; + UnixSocketAddress address; socklen_t addressLength; makeUnixSocketAddress(true, port, socketPath, &address, &addressLength); @@ -1212,7 +1286,11 @@ static rem_port* unix_listener_socket(rem_port* port, USHORT flag, const TEXT* s inet_error(true, port, "accept", isc_net_connect_err, inetErrNo); } +#ifdef WIN_NT + if (flag & SRVR_debug) +#else if ((flag & SRVR_debug) || !fork()) +#endif { SOCLOSE(port->port_handle); port->port_handle = s; @@ -1222,16 +1300,30 @@ static rem_port* unix_listener_socket(rem_port* port, USHORT flag, const TEXT* s return port; } +#ifdef WIN_NT + MutexLockGuard forkGuard(forkMutex, FB_FUNCTION); + if (!forkThreadStarted) + { + forkThreadStarted = true; + forkEvent = CreateEvent(NULL, FALSE, FALSE, NULL); + forkSockets = FB_NEW ForkSocketsArray(*getDefaultMemoryPool()); + + Thread::start(forkThread, (void*) flag, THREAD_medium); + } + forkSockets->add(ForkSocket(s, true)); + SetEvent(forkEvent); +#else MutexLockGuard guard(waitThreadMutex, FB_FUNCTION); if (!procCount++) Thread::start(waitThread, 0, THREAD_medium); SOCLOSE(s); +#endif } } -#endif // !WIN_NT && HAVE_SYS_UN_H +#endif // HAVE_AF_UNIX_SUPPORT static rem_port* listener_socket(rem_port* port, USHORT flag, const addrinfo* pai) @@ -1376,11 +1468,11 @@ static rem_port* listener_socket(rem_port* port, USHORT flag, const addrinfo* pa { forkThreadStarted = true; forkEvent = CreateEvent(NULL, FALSE, FALSE, NULL); - forkSockets = FB_NEW SocketsArray(*getDefaultMemoryPool()); + forkSockets = FB_NEW ForkSocketsArray(*getDefaultMemoryPool()); Thread::start(forkThread, (void*) flag, THREAD_medium); } - forkSockets->add(s); + forkSockets->add(ForkSocket(s, false)); SetEvent(forkEvent); #else MutexLockGuard guard(waitThreadMutex, FB_FUNCTION); @@ -1407,7 +1499,7 @@ static rem_port* listener_socket(rem_port* port, USHORT flag, const addrinfo* pa } -rem_port* INET_reconnect(SOCKET handle) +rem_port* INET_reconnect(SOCKET handle, bool unixSocket) { /************************************** * @@ -1427,12 +1519,34 @@ rem_port* INET_reconnect(SOCKET handle) port->port_flags |= PORT_server; port->port_server_flags |= SRVR_server; - if (! setKeepAlive(port->port_handle)) { - gds__log("inet server err: setting KEEPALIVE socket option \n"); +#ifdef HAVE_AF_UNIX_SUPPORT + if (unixSocket) + { + port->port_flags |= PORT_unix; + port->port_protocol_id = "UNIX"; + + UnixSocketAddress address; + socklen_t addressLength = sizeof(address); + memset(&address, 0, sizeof(address)); + + if (getsockname(port->port_handle, reinterpret_cast(&address), &addressLength) == 0 && + address.sun_path[0]) + { + port->port_address = address.sun_path; + } + else + gds__log("inet server err: getting Unix socket name \n"); } + else +#endif + { + if (! setKeepAlive(port->port_handle)) { + gds__log("inet server err: setting KEEPALIVE socket option \n"); + } - if (! setNoNagleOption(port)) { - gds__log("inet server err: setting NODELAY socket option \n"); + if (! setNoNagleOption(port)) { + gds__log("inet server err: setting NODELAY socket option \n"); + } } return port; @@ -1708,7 +1822,7 @@ static rem_port* aux_connect(rem_port* port, PACKET* packet) if (port->port_flags & PORT_unix) { -#if !defined(WIN_NT) && defined(HAVE_SYS_UN_H) +#ifdef HAVE_AF_UNIX_SUPPORT removeUnixSocketPath(port->port_address); #endif port->port_protocol_id = "UNIX"; @@ -1727,14 +1841,14 @@ static rem_port* aux_connect(rem_port* port, PACKET* packet) new_port->port_dummy_timeout = new_port->port_dummy_packet_interval; P_RESP* response = &packet->p_resp; -#if !defined(WIN_NT) && defined(HAVE_SYS_UN_H) +#ifdef HAVE_AF_UNIX_SUPPORT if (port->port_flags & PORT_unix) { string auxPath; auxPath.assign(reinterpret_cast(response->p_resp_data.cstr_address), response->p_resp_data.cstr_length); - sockaddr_un address; + UnixSocketAddress address; socklen_t addressLength; makeUnixSocketAddress(false, port, auxPath.c_str(), &address, &addressLength); @@ -1829,12 +1943,12 @@ static rem_port* aux_request( rem_port* port, PACKET* packet) * **************************************/ -#if !defined(WIN_NT) && defined(HAVE_SYS_UN_H) +#ifdef HAVE_AF_UNIX_SUPPORT if (port->port_flags & PORT_unix) { const string auxPath = makeAuxUnixSocketPath(port->port_address); - sockaddr_un address; + UnixSocketAddress address; socklen_t addressLength; makeUnixSocketAddress(false, port, auxPath.c_str(), &address, &addressLength); @@ -2041,7 +2155,7 @@ static void disconnect(rem_port* port) port->port_state = rem_port::DISCONNECTED; -#if !defined(WIN_NT) && defined(HAVE_SYS_UN_H) +#ifdef HAVE_AF_UNIX_SUPPORT if ((port->port_flags & PORT_unix_unlink) && (port->port_flags & PORT_unix)) removeUnixSocketPath(port->port_address); #endif @@ -2197,7 +2311,7 @@ static int wsaExitHandler(const int, const int, void*) } -static int fork(SOCKET old_handle, USHORT flag) +static int fork(SOCKET old_handle, USHORT flag, bool unixSocket) { /************************************** * @@ -2222,7 +2336,8 @@ static int fork(SOCKET old_handle, USHORT flag) } string cmdLine; - cmdLine.printf("%s -i -h %" HANDLEFORMAT"@%" ULONGFORMAT, name, new_handle, GetCurrentProcessId()); + cmdLine.printf("%s -i%s -h %" HANDLEFORMAT"@%" ULONGFORMAT, + name, unixSocket ? " -u" : "", new_handle, GetCurrentProcessId()); STARTUPINFO start_crud; start_crud.cb = sizeof(STARTUPINFO); @@ -2263,18 +2378,18 @@ THREAD_ENTRY_DECLARE forkThread(THREAD_ENTRY_PARAM arg) while (!INET_shutting_down) { - SOCKET s = 0; + ForkSocket forkSocket; { // scope MutexLockGuard forkGuard(forkMutex, FB_FUNCTION); if (!forkSockets || forkSockets->getCount() == 0) break; - s = (*forkSockets)[0]; + forkSocket = (*forkSockets)[0]; forkSockets->remove((FB_SIZE_T) 0); } - fork(s, flag); - SOCLOSE(s); + fork(forkSocket.socket, flag, forkSocket.unixSocket); + SOCLOSE(forkSocket.socket); } } diff --git a/src/remote/inet_proto.h b/src/remote/inet_proto.h index 0db635a6dd9..f0ad7b8fe5a 100644 --- a/src/remote/inet_proto.h +++ b/src/remote/inet_proto.h @@ -38,7 +38,7 @@ rem_port* INET_analyze(ClntAuthBlock*, const Firebird::PathName&, const TEXT*, const Firebird::PathName*, Firebird::ICryptKeyCallback*, int af = AF_UNSPEC); rem_port* INET_connect(const TEXT*, struct packet*, USHORT, Firebird::ClumpletReader*, Firebird::RefPtr*, int af = AF_UNSPEC); -rem_port* INET_reconnect(SOCKET); +rem_port* INET_reconnect(SOCKET, bool unixSocket); rem_port* INET_server(SOCKET); void setStopMainThread(FPTR_INT func); diff --git a/src/remote/server/os/win32/srvr_w32.cpp b/src/remote/server/os/win32/srvr_w32.cpp index c1658460cba..20223510f30 100644 --- a/src/remote/server/os/win32/srvr_w32.cpp +++ b/src/remote/server/os/win32/srvr_w32.cpp @@ -121,7 +121,7 @@ static THREAD_ENTRY_DECLARE inet_connect_wait_thread(THREAD_ENTRY_PARAM); static THREAD_ENTRY_DECLARE xnet_connect_wait_thread(THREAD_ENTRY_PARAM); static THREAD_ENTRY_DECLARE start_connections_thread(THREAD_ENTRY_PARAM); static THREAD_ENTRY_DECLARE process_connection_thread(THREAD_ENTRY_PARAM); -static HANDLE parse_args(LPCSTR, USHORT*); +static HANDLE parse_args(LPCSTR, USHORT*, bool*); static void service_connection(rem_port*); static int wait_threads(const int reason, const int mask, void* arg); @@ -230,7 +230,8 @@ int WINAPI WinMain(HINSTANCE hThisInst, HINSTANCE /*hPrevInst*/, LPSTR lpszArgs, if (Config::getServerMode() != MODE_CLASSIC) server_flag = SRVR_multi_client; - const HANDLE connection_handle = parse_args(lpszArgs, &server_flag); + bool connectionUnixSocket = false; + const HANDLE connection_handle = parse_args(lpszArgs, &server_flag, &connectionUnixSocket); // get priority class from the config file int priority = Config::getProcessPriorityLevel(); @@ -286,7 +287,7 @@ int WINAPI WinMain(HINSTANCE hThisInst, HINSTANCE /*hPrevInst*/, LPSTR lpszArgs, { if (server_flag & SRVR_inet) { - port = INET_reconnect((SOCKET) connection_handle); + port = INET_reconnect((SOCKET) connection_handle, connectionUnixSocket); if (port) { @@ -549,7 +550,7 @@ static THREAD_ENTRY_DECLARE start_connections_thread(THREAD_ENTRY_PARAM) } -static HANDLE parse_args(LPCSTR lpszArgs, USHORT* pserver_flag) +static HANDLE parse_args(LPCSTR lpszArgs, USHORT* pserver_flag, bool* connectionUnixSocket) { /************************************** * @@ -669,6 +670,10 @@ static HANDLE parse_args(LPCSTR lpszArgs, USHORT* pserver_flag) *pserver_flag &= ~SRVR_high_priority; break; + case 'U': + *connectionUnixSocket = true; + break; + case 'S': delimited = false; while (*p && *p == ' ')