From 166db74597e2bb9dc1396ed90852a06df8697fed Mon Sep 17 00:00:00 2001 From: Hackerpilot Date: Fri, 15 Jan 2016 00:49:08 -0800 Subject: [PATCH] Implement #217 --- src/client/client.d | 72 +++++++++++++++++++++------ src/common/socket.d | 45 +++++++++++++++++ src/server/server.d | 118 ++++++++++++++++++++++++++++++++------------ 3 files changed, 188 insertions(+), 47 deletions(-) create mode 100644 src/common/socket.d diff --git a/src/client/client.d b/src/client/client.d index b3d47be..515baae 100644 --- a/src/client/client.d +++ b/src/client/client.d @@ -33,6 +33,7 @@ import std.experimental.logger; import msgpack; import common.messages; import common.dcd_version; +import common.socket; int main(string[] args) { @@ -48,6 +49,16 @@ int main(string[] args) bool printVersion; bool listImports; string search; + version(Windows) + { + bool useTCP = true; + string socketFile; + } + else + { + bool useTCP = false; + string socketFile = generateSocketName(); + } try { @@ -55,7 +66,8 @@ int main(string[] args) "port|p", &port, "help|h", &help, "shutdown", &shutdown, "clearCache", &clearCache, "symbolLocation|l", &symbolLocation, "doc|d", &doc, "query|status|q", &query, "search|s", &search, - "version", &printVersion, "listImports", &listImports); + "version", &printVersion, "listImports", &listImports, + "tcp", &useTCP, "socketFile", &socketFile); } catch (ConvException e) { @@ -66,6 +78,12 @@ int main(string[] args) AutocompleteRequest request; + if (help) + { + printHelp(args[0]); + return 0; + } + if (printVersion) { version (Windows) @@ -76,16 +94,21 @@ int main(string[] args) write(DCD_VERSION, " ", GIT_HASH); return 0; } - else if (help) + + version (Windows) if (socketFile !is null) { - printHelp(args[0]); - return 0; + fatal("UNIX domain sockets not supported on Windows"); + return 1; } - else if (query) + + if (useTCP) + socketFile = null; + + if (query) { try { - TcpSocket socket = createSocket(port); + Socket socket = createSocket(socketFile, port); scope (exit) { socket.shutdown(SocketShutdown.BOTH); socket.close(); } request.kind = RequestKind.query; if (sendRequest(socket, request)) @@ -112,7 +135,7 @@ int main(string[] args) request.kind = RequestKind.shutdown; else if (clearCache) request.kind = RequestKind.clearCache; - TcpSocket socket = createSocket(port); + Socket socket = createSocket(socketFile, port); scope (exit) { socket.shutdown(SocketShutdown.BOTH); socket.close(); } return sendRequest(socket, request) ? 0 : 1; } @@ -122,7 +145,7 @@ int main(string[] args) request.importPaths = importPaths.map!(a => absolutePath(a)).array; if (cursorPos == size_t.max) { - TcpSocket socket = createSocket(port); + Socket socket = createSocket(socketFile, port); scope (exit) { socket.shutdown(SocketShutdown.BOTH); socket.close(); } if (!sendRequest(socket, request)) return 1; @@ -132,7 +155,7 @@ int main(string[] args) else if (listImports) { request.kind |= RequestKind.listImports; - TcpSocket socket = createSocket(port); + Socket socket = createSocket(socketFile, port); scope (exit) { socket.shutdown(SocketShutdown.BOTH); socket.close(); } sendRequest(socket, request); AutocompleteResponse response = getResponse(socket); @@ -194,7 +217,7 @@ int main(string[] args) request.kind |= RequestKind.autocomplete; // Send message to server - TcpSocket socket = createSocket(port); + Socket socket = createSocket(socketFile, port); scope (exit) { socket.shutdown(SocketShutdown.BOTH); socket.close(); } if (!sendRequest(socket, request)) return 1; @@ -266,16 +289,33 @@ Options: --port PORTNUMBER | -p PORTNUMBER Uses PORTNUMBER to communicate with the server instead of the default - port 9166.`, programName); + port 9166. Only used on Windows or when the --tcp option is set. + + --tcp + Send requests on a TCP socket instead of a UNIX domain socket. This + switch has no effect on Windows. + + --socketFile FILENAME + Use the given FILENAME as the path to the UNIX domain socket. Using + this switch is an error on Windows.`, programName); } -TcpSocket createSocket(ushort port) +Socket createSocket(string socketFile, ushort port) { import core.time : dur; - TcpSocket socket = new TcpSocket(AddressFamily.INET); + Socket socket; + if (socketFile is null) + { + socket = new TcpSocket(AddressFamily.INET); + socket.connect(new InternetAddress("localhost", port)); + } + else + { + socket = new Socket(AddressFamily.UNIX, SocketType.STREAM); + socket.connect(new UnixAddress(socketFile)); + } socket.setOption(SocketOptionLevel.SOCKET, SocketOption.RCVTIMEO, dur!"seconds"(5)); - socket.connect(new InternetAddress("localhost", port)); socket.blocking = true; return socket; } @@ -283,7 +323,7 @@ TcpSocket createSocket(ushort port) /** * Returns: true on success */ -bool sendRequest(TcpSocket socket, AutocompleteRequest request) +bool sendRequest(Socket socket, AutocompleteRequest request) { ubyte[] message = msgpack.pack(request); ubyte[] messageBuffer = new ubyte[message.length + message.length.sizeof]; @@ -296,7 +336,7 @@ bool sendRequest(TcpSocket socket, AutocompleteRequest request) /** * Gets the response from the server */ -AutocompleteResponse getResponse(TcpSocket socket) +AutocompleteResponse getResponse(Socket socket) { ubyte[1024 * 16] buffer; auto bytesReceived = socket.receive(buffer); diff --git a/src/common/socket.d b/src/common/socket.d new file mode 100644 index 0000000..05623f6 --- /dev/null +++ b/src/common/socket.d @@ -0,0 +1,45 @@ +/** + * This file is part of DCD, a development tool for the D programming language. + * Copyright (C) 2015 Brian Schott + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +module common.socket; + +import core.sys.posix.unistd; // getuid +import std.format; +import std.process; +import std.path; + +version (OSX) version = haveUnixSockets; +version (linux) version = haveUnixSockets; +version (BSD) version = haveUnixSockets; +version (FreeBSD) version = haveUnixSockets; + +string generateSocketName() +{ + version (haveUnixSockets) + { + immutable string socketFileName = "dcd-%d.socket".format(getuid()); + version (OSX) + return buildPath("/", "var", "tmp", socketFileName); + else + { + immutable string xdg = environment.get("XDG_RUNTIME_DIR"); + return xdg is null ? buildPath("/", "tmp", socketFileName) : buildPath(xdg, + "dcd.socket"); + } + } +} diff --git a/src/server/server.d b/src/server/server.d index 62e89be..406dc32 100644 --- a/src/server/server.d +++ b/src/server/server.d @@ -18,30 +18,33 @@ module server.server; -import std.socket; -import std.stdio; -import std.getopt; +import core.sys.posix.sys.stat; import std.algorithm; -import std.path; -import std.file; import std.array; -import std.process; -import std.datetime; import std.conv; +import std.datetime; +import std.exception : enforce; import std.experimental.allocator; import std.experimental.allocator.mallocator; -import std.exception : enforce; import std.experimental.logger; +import std.file; +import std.file; +import std.getopt; +import std.path; +import std.process; +import std.socket; +import std.stdio; import msgpack; import dsymbol.string_interning; +import common.dcd_version; import common.messages; -import server.autocomplete; +import common.socket; import dsymbol.modulecache; import dsymbol.symbol; -import common.dcd_version; +import server.autocomplete; /// Name of the server configuration file enum CONFIG_FILE_NAME = "dcd.conf"; @@ -59,12 +62,22 @@ int main(string[] args) bool ignoreConfig; string[] importPaths; LogLevel level = globalLogLevel; + version(Windows) + { + bool useTCP = true; + string socketFile; + } + else + { + bool useTCP = false; + string socketFile = generateSocketName(); + } try { getopt(args, "port|p", &port, "I", &importPaths, "help|h", &help, "version", &printVersion, "ignoreConfig", &ignoreConfig, - "logLevel", &level); + "logLevel", &level, "tcp", &useTCP, "socketFile", &socketFile); } catch (ConvException e) { @@ -73,11 +86,6 @@ int main(string[] args) return 1; } - globalLogLevel = level; - - info("Starting up..."); - StopWatch sw = StopWatch(AutoStart.yes); - if (printVersion) { version (Windows) @@ -95,19 +103,52 @@ int main(string[] args) return 0; } + version (Windows) if (socketFile !is null) + { + fatal("UNIX domain sockets not supported on Windows"); + return 1; + } + + globalLogLevel = level; + + info("Starting up..."); + StopWatch sw = StopWatch(AutoStart.yes); + if (!ignoreConfig) importPaths ~= loadConfiguredImportDirs(); - auto socket = new TcpSocket(AddressFamily.INET); - socket.blocking = true; - socket.setOption(SocketOptionLevel.SOCKET, SocketOption.REUSEADDR, true); - socket.bind(new InternetAddress("localhost", port)); + Socket socket; + if (useTCP) + { + socket = new TcpSocket(AddressFamily.INET); + socket.blocking = true; + socket.setOption(SocketOptionLevel.SOCKET, SocketOption.REUSEADDR, true); + socket.bind(new InternetAddress("localhost", port)); + info("Listening on port ", port); + } + else + { + socket = new Socket(AddressFamily.UNIX, SocketType.STREAM); + if (exists(socketFile)) + { + info("Cleaning up old socket file at ", socketFile); + remove(socketFile); + } + socket.blocking = true; + socket.setOption(SocketOptionLevel.SOCKET, SocketOption.REUSEADDR, true); + socket.bind(new UnixAddress(socketFile)); + setAttributes(socketFile, S_IRUSR | S_IWUSR); + info("Listening at ", socketFile); + } socket.listen(0); + scope (exit) { info("Shutting down sockets..."); socket.shutdown(SocketShutdown.BOTH); socket.close(); + if (!useTCP) + remove(socketFile); info("Sockets shut down."); } @@ -126,22 +167,28 @@ int main(string[] args) version (Posix) chdir("/"); version (LittleEndian) - immutable expectedClient = IPv4Union([127, 0, 0, 1]); - else immutable expectedClient = IPv4Union([1, 0, 0, 127]); + else + immutable expectedClient = IPv4Union([127, 0, 0, 1]); serverLoop: while (true) { auto s = socket.accept(); s.blocking = true; - // Only accept connections from localhost - IPv4Union actual; - InternetAddress clientAddr = cast(InternetAddress) s.remoteAddress(); - actual.i = clientAddr.addr; - // Shut down if somebody tries connecting from outside - enforce(actual.i = expectedClient.i, "Connection attempted from " - ~ clientAddr.toAddrString()); + if (useTCP) + { + // Only accept connections from localhost + IPv4Union actual; + InternetAddress clientAddr = cast(InternetAddress) s.remoteAddress(); + actual.i = clientAddr.addr; + // Shut down if somebody tries connecting from outside + if (actual.i != expectedClient.i) + { + fatal("Connection attempted from ", clientAddr.toAddrString()); + return 1; + } + } scope (exit) { @@ -340,9 +387,18 @@ options: Prints the version number and then exits. --port PORTNUMBER | -pPORTNUMBER - Listens on PORTNUMBER instead of the default port 9166. + Listens on PORTNUMBER instead of the default port 9166 when TCP sockets + are used. --logLevel LEVEL The logging level. Valid values are 'all', 'trace', 'info', 'warning', - 'error', 'critical', 'fatal', and 'off'`, programName); + 'error', 'critical', 'fatal', and 'off'. + + --tcp + Listen on a TCP socket instead of a UNIX domain socket. This switch + has no effect on Windows. + + --socketFile FILENAME + Use the given FILENAME as the path to the UNIX domain socket. Using + this switch is an error on Windows.`, programName); }