1 /** 2 * This file is part of DCD, a development tool for the D programming language. 3 * Copyright (C) 2014 Brian Schott 4 * 5 * This program is free software: you can redistribute it and/or modify 6 * it under the terms of the GNU General Public License as published by 7 * the Free Software Foundation, either version 3 of the License, or 8 * (at your option) any later version. 9 * 10 * This program is distributed in the hope that it will be useful, 11 * but WITHOUT ANY WARRANTY; without even the implied warranty of 12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 * GNU General Public License for more details. 14 * 15 * You should have received a copy of the GNU General Public License 16 * along with this program. If not, see <http://www.gnu.org/licenses/>. 17 */ 18 19 module server.main; 20 21 import core.sys.posix.sys.stat; 22 import std.algorithm; 23 import std.array; 24 import std.conv; 25 import std.datetime; 26 import std.exception : enforce; 27 import std.experimental.allocator; 28 import std.experimental.allocator.mallocator; 29 import std.experimental.logger; 30 import std.file; 31 import std.getopt; 32 import std.path: buildPath; 33 import std.process; 34 import std.socket; 35 import std.stdio; 36 37 import msgpack; 38 39 import common.dcd_version; 40 import common.messages; 41 import common.socket; 42 import dsymbol.modulecache; 43 import server.autocomplete; 44 import server.server; 45 46 int main(string[] args) 47 { 48 ushort port; 49 bool help; 50 bool printVersion; 51 bool ignoreConfig; 52 string[] importPaths; 53 LogLevel level = globalLogLevel; 54 version(Windows) 55 { 56 bool useTCP = true; 57 string socketFile; 58 } 59 else 60 { 61 bool useTCP = false; 62 string socketFile = generateSocketName(); 63 } 64 65 sharedLog.fatalHandler = () {}; 66 67 try 68 { 69 getopt(args, "port|p", &port, "I", &importPaths, "help|h", &help, 70 "version", &printVersion, "ignoreConfig", &ignoreConfig, 71 "logLevel", &level, "tcp", &useTCP, "socketFile", &socketFile); 72 } 73 catch (ConvException e) 74 { 75 fatal(e.msg); 76 printHelp(args[0]); 77 return 1; 78 } 79 80 globalLogLevel = level; 81 82 if (printVersion) 83 { 84 version (Windows) 85 writeln(DCD_VERSION); 86 else version (built_with_dub) 87 writeln(DCD_VERSION); 88 else 89 write(DCD_VERSION, " ", GIT_HASH); 90 return 0; 91 } 92 93 if (help) 94 { 95 printHelp(args[0]); 96 return 0; 97 } 98 99 // If the user specified a port number, assume that they wanted a TCP 100 // connection. Otherwise set the port number to the default and let the 101 // useTCP flag deterimen what to do later. 102 if (port != 0) 103 useTCP = true; 104 else 105 port = DEFAULT_PORT_NUMBER; 106 107 if (useTCP) 108 socketFile = null; 109 110 version (Windows) if (socketFile !is null) 111 { 112 fatal("UNIX domain sockets not supported on Windows"); 113 return 1; 114 } 115 116 if (serverIsRunning(useTCP, socketFile, port)) 117 { 118 fatal("Another instance of DCD-server is already running"); 119 return 1; 120 } 121 122 info("Starting up..."); 123 StopWatch sw = StopWatch(AutoStart.yes); 124 125 if (!ignoreConfig) 126 importPaths ~= loadConfiguredImportDirs(); 127 128 Socket socket; 129 if (useTCP) 130 { 131 socket = new TcpSocket(AddressFamily.INET); 132 socket.blocking = true; 133 socket.setOption(SocketOptionLevel.SOCKET, SocketOption.REUSEADDR, true); 134 socket.bind(new InternetAddress("localhost", port)); 135 info("Listening on port ", port); 136 } 137 else 138 { 139 version(Windows) 140 { 141 fatal("UNIX domain sockets not supported on Windows"); 142 return 1; 143 } 144 else 145 { 146 socket = new Socket(AddressFamily.UNIX, SocketType.STREAM); 147 if (exists(socketFile)) 148 { 149 info("Cleaning up old socket file at ", socketFile); 150 remove(socketFile); 151 } 152 socket.blocking = true; 153 socket.setOption(SocketOptionLevel.SOCKET, SocketOption.REUSEADDR, true); 154 socket.bind(new UnixAddress(socketFile)); 155 setAttributes(socketFile, S_IRUSR | S_IWUSR); 156 info("Listening at ", socketFile); 157 } 158 } 159 socket.listen(32); 160 161 scope (exit) 162 { 163 info("Shutting down sockets..."); 164 socket.shutdown(SocketShutdown.BOTH); 165 socket.close(); 166 if (!useTCP) 167 remove(socketFile); 168 info("Sockets shut down."); 169 } 170 171 ModuleCache cache = ModuleCache(new ASTAllocator); 172 cache.addImportPaths(importPaths); 173 infof("Import directories:\n %-(%s\n %)", cache.getImportPaths()); 174 175 ubyte[] buffer = cast(ubyte[]) Mallocator.instance.allocate(1024 * 1024 * 4); // 4 megabytes should be enough for anybody... 176 scope(exit) Mallocator.instance.deallocate(buffer); 177 178 sw.stop(); 179 info(cache.symbolsAllocated, " symbols cached."); 180 info("Startup completed in ", sw.peek().to!("msecs", float), " milliseconds."); 181 182 // No relative paths 183 version (Posix) chdir("/"); 184 185 version (LittleEndian) 186 immutable expectedClient = IPv4Union([1, 0, 0, 127]); 187 else 188 immutable expectedClient = IPv4Union([127, 0, 0, 1]); 189 190 serverLoop: while (true) 191 { 192 auto s = socket.accept(); 193 s.blocking = true; 194 195 if (useTCP) 196 { 197 // Only accept connections from localhost 198 IPv4Union actual; 199 InternetAddress clientAddr = cast(InternetAddress) s.remoteAddress(); 200 actual.i = clientAddr.addr; 201 // Shut down if somebody tries connecting from outside 202 if (actual.i != expectedClient.i) 203 { 204 fatal("Connection attempted from ", clientAddr.toAddrString()); 205 return 1; 206 } 207 } 208 209 scope (exit) 210 { 211 s.shutdown(SocketShutdown.BOTH); 212 s.close(); 213 } 214 ptrdiff_t bytesReceived = s.receive(buffer); 215 216 auto requestWatch = StopWatch(AutoStart.yes); 217 218 size_t messageLength; 219 // bit magic! 220 (cast(ubyte*) &messageLength)[0..size_t.sizeof] = buffer[0..size_t.sizeof]; 221 while (bytesReceived < messageLength + size_t.sizeof) 222 { 223 immutable b = s.receive(buffer[bytesReceived .. $]); 224 if (b == Socket.ERROR) 225 { 226 bytesReceived = Socket.ERROR; 227 break; 228 } 229 bytesReceived += b; 230 } 231 232 if (bytesReceived == Socket.ERROR) 233 { 234 warning("Socket recieve failed"); 235 break; 236 } 237 238 AutocompleteRequest request; 239 msgpack.unpack(buffer[size_t.sizeof .. bytesReceived], request); 240 if (request.kind & RequestKind.clearCache) 241 { 242 info("Clearing cache."); 243 cache.clear(); 244 } 245 else if (request.kind & RequestKind.shutdown) 246 { 247 info("Shutting down."); 248 break serverLoop; 249 } 250 else if (request.kind & RequestKind.query) 251 { 252 AutocompleteResponse response; 253 response.completionType = "ack"; 254 ubyte[] responseBytes = msgpack.pack(response); 255 s.send(responseBytes); 256 continue; 257 } 258 if (request.kind & RequestKind.addImport) 259 { 260 cache.addImportPaths(request.importPaths); 261 } 262 if (request.kind & RequestKind.listImports) 263 { 264 AutocompleteResponse response; 265 response.importPaths = cache.getImportPaths().map!(a => cast() a).array(); 266 ubyte[] responseBytes = msgpack.pack(response); 267 info("Returning import path list"); 268 s.send(responseBytes); 269 } 270 else if (request.kind & RequestKind.autocomplete) 271 { 272 info("Getting completions"); 273 AutocompleteResponse response = complete(request, cache); 274 ubyte[] responseBytes = msgpack.pack(response); 275 s.send(responseBytes); 276 } 277 else if (request.kind & RequestKind.doc) 278 { 279 info("Getting doc comment"); 280 try 281 { 282 AutocompleteResponse response = getDoc(request, cache); 283 ubyte[] responseBytes = msgpack.pack(response); 284 s.send(responseBytes); 285 } 286 catch (Exception e) 287 { 288 warning("Could not get DDoc information", e.msg); 289 } 290 } 291 else if (request.kind & RequestKind.symbolLocation) 292 { 293 try 294 { 295 AutocompleteResponse response = findDeclaration(request, cache); 296 ubyte[] responseBytes = msgpack.pack(response); 297 s.send(responseBytes); 298 } 299 catch (Exception e) 300 { 301 warning("Could not get symbol location", e.msg); 302 } 303 } 304 else if (request.kind & RequestKind.search) 305 { 306 AutocompleteResponse response = symbolSearch(request, cache); 307 ubyte[] responseBytes = msgpack.pack(response); 308 s.send(responseBytes); 309 } 310 else if (request.kind & RequestKind.localUse) 311 { 312 try 313 { 314 AutocompleteResponse response = findLocalUse(request, cache); 315 ubyte[] responseBytes = msgpack.pack(response); 316 s.send(responseBytes); 317 } 318 catch (Exception e) 319 { 320 warning("Could not find local usage", e.msg); 321 } 322 } 323 info("Request processed in ", requestWatch.peek().to!("msecs", float), " milliseconds"); 324 } 325 return 0; 326 } 327 328 /// IP v4 address as bytes and a uint 329 union IPv4Union 330 { 331 /// the bytes 332 ubyte[4] b; 333 /// the uint 334 uint i; 335 } 336 337 import std.regex : ctRegex; 338 alias envVarRegex = ctRegex!(`\$\{([_a-zA-Z][_a-zA-Z 0-9]*)\}`); 339 340 private unittest 341 { 342 import std.regex : replaceAll; 343 344 enum input = `${HOME}/aaa/${_bb_b}/ccc`; 345 346 assert(replaceAll!(m => m[1])(input, envVarRegex) == `HOME/aaa/_bb_b/ccc`); 347 } 348 349 /** 350 * Implements the --help switch. 351 */ 352 void printHelp(string programName) 353 { 354 writefln( 355 ` 356 Usage: %s options 357 358 options: 359 -I PATH 360 Includes PATH in the listing of paths that are searched for file 361 imports. 362 363 --help | -h 364 Prints this help message. 365 366 --version 367 Prints the version number and then exits. 368 369 --port PORTNUMBER | -pPORTNUMBER 370 Listens on PORTNUMBER instead of the default port 9166 when TCP sockets 371 are used. 372 373 --logLevel LEVEL 374 The logging level. Valid values are 'all', 'trace', 'info', 'warning', 375 'error', 'critical', 'fatal', and 'off'. 376 377 --tcp 378 Listen on a TCP socket instead of a UNIX domain socket. This switch 379 has no effect on Windows. 380 381 --socketFile FILENAME 382 Use the given FILENAME as the path to the UNIX domain socket. Using 383 this switch is an error on Windows.`, programName); 384 }