1 /******************************************************************************* 2 3 Unix domain socket listener. 4 5 Copyright: 6 Copyright (c) 2009-2016 dunnhumby Germany GmbH. 7 All rights reserved. 8 9 License: 10 Boost Software License Version 1.0. See LICENSE_BOOST.txt for details. 11 Alternatively, this file may be distributed under the terms of the Tango 12 3-Clause BSD License (see LICENSE_BSD.txt for details). 13 14 *******************************************************************************/ 15 16 module ocean.net.server.unix.UnixListener; 17 18 19 import ocean.net.server.unix.UnixConnectionHandler; 20 import ocean.net.server.SelectListener; 21 import ocean.io.select.EpollSelectDispatcher; 22 23 import ocean.transition; 24 25 /// Provides default functionality for handling unix socket commands. 26 public class UnixListener : UnixSocketListener!( BasicCommandHandler ) 27 { 28 /// Provide basic command handling functionality. 29 public BasicCommandHandler handler; 30 31 /*********************************************************************** 32 33 Constructor to create the basic command handler directly from 34 an array of handlers. 35 36 Params: 37 address_path = the file path i.e. addreBasicCommandHandlerss of the Unix domain 38 server socket 39 epoll = the `EpollSelectDispatcher` instance to use for 40 I/O (connection handler parameter) 41 handlers = Array of command to handler delegate. 42 mode = mode to apply after binding the socket file 43 44 Throws: 45 `Exception` if 46 - `path` is too long; `path.length` must be less than 47 `UNIX_PATH_MAX`, 48 - an error occurred creating or binding the server socket. 49 50 ***********************************************************************/ 51 52 public this ( cstring address_path, EpollSelectDispatcher epoll, 53 scope BasicCommandHandler.Handler[istring] handlers, 54 int mode = -1 ) 55 { 56 this.handler = new BasicCommandHandler(handlers); 57 super(address_path, epoll, this.handler, mode); 58 } 59 60 /*********************************************************************** 61 62 Constructor to create the basic command handler directly from 63 an array of handlers with support for interactive sessions. 64 65 Params: 66 address_path = the file path i.e. addreBasicCommandHandlerss of the Unix domain 67 server socket 68 epoll = the `EpollSelectDispatcher` instance to use for 69 I/O (connection handler parameter) 70 handlers = Array of command to handler delegate. 71 interactive_handlers = Array of command to interactive handler delegate. 72 mode = mode to apply after binding the socket file 73 74 Throws: 75 `Exception` if 76 - `path` is too long; `path.length` must be less than 77 `UNIX_PATH_MAX`, 78 - an error occurred creating or binding the server socket. 79 80 ***********************************************************************/ 81 82 public this ( istring address_path, EpollSelectDispatcher epoll, 83 scope BasicCommandHandler.Handler[istring] handlers, 84 scope BasicCommandHandler.InteractiveHandler[istring] interactive_handlers, 85 int mode = -1 ) 86 { 87 this.handler = new BasicCommandHandler(handlers, interactive_handlers); 88 super(address_path, epoll, this.handler, mode); 89 } 90 } 91 92 /******************************************************************************* 93 94 Params: 95 CommandHandlerType = The request handler to use when processing commands. 96 The type is passed as the template argument of 97 UnixConnectionHandler and is assumed to have a 98 callable member `void handle ( cstring, cstring, 99 void delegate ( cstring ), 100 void delegate ( ref mstring ))`. 101 102 *******************************************************************************/ 103 104 public class UnixSocketListener ( CommandHandlerType ) : SelectListener!( 105 UnixSocketConnectionHandler!(CommandHandlerType), EpollSelectDispatcher, 106 CommandHandlerType, 107 cstring // address_path 108 ) 109 { 110 import ocean.sys.socket.UnixSocket; 111 import ocean.stdc.posix.sys.un: sockaddr_un; 112 import ocean.stdc.posix.sys.socket: AF_UNIX, sockaddr; 113 import ocean.text.convert.Formatter; 114 115 import core.sys.posix.sys.stat; 116 import core.sys.posix.unistd: unlink; 117 import core.stdc.errno: errno; 118 119 import ocean.stdc.string: strerror_r, strlen; 120 121 import ocean.core.Enforce; 122 123 import ocean.util.log.Logger; 124 125 import core.sys.posix.sys.stat: umask; 126 127 /*************************************************************************** 128 129 '\0'-terminated socket address path 130 131 ***************************************************************************/ 132 133 private char[sockaddr_un.sun_path.length] address_pathnul; 134 135 /*************************************************************************** 136 137 Constructor. 138 139 `address_path` is a file path that serves as the Unix domain server 140 socket address. If it exists, it will be deleted and recreated. 141 142 Params: 143 address_path = the file path i.e. address of the Unix domain server 144 socket 145 epoll = the `EpollSelectDispatcher` instance to use for I/O 146 (connection handler parameter) 147 handler = Command handler. 148 mode = mode to apply after binding the socket file 149 150 Throws: 151 `Exception` if 152 - `path` is too long; `path.length` must be less than 153 `UNIX_PATH_MAX`, 154 - an error occurred creating or binding the server socket. 155 156 ***************************************************************************/ 157 158 public this ( cstring address_path, EpollSelectDispatcher epoll, 159 CommandHandlerType handler, int mode = -1 ) 160 { 161 enforce(address_path.length < sockaddr_un.sun_path.length, 162 format("Unix socket path too long: {}", address_path)); 163 164 snformat(this.address_pathnul, "{}\0", address_path); 165 166 auto log = Log.lookup("ocean.net.server.unixsocket"); 167 168 // Don't report an error if unlink() fails. In any case success or 169 // failure is solely up to the super constructor. 170 if (!unlink(this.address_pathnul.ptr)) 171 { 172 log.warn("Deleted existing socket \"{}\"", address_path); 173 } 174 175 try 176 { 177 sockaddr_un address; 178 address.sun_family = AF_UNIX; 179 address.sun_path[0 .. this.address_pathnul.length] = 180 this.address_pathnul; 181 182 // The socket should be opened with rw-rw-r-- permissions, 183 // so the owner and group could connect to it by default. 184 auto old_umask = umask(Octal!("002")); 185 scope (exit) 186 umask(old_umask); 187 188 super(cast(sockaddr*)&address, new UnixSocket, 189 epoll, handler, address_path); 190 191 if (mode >= 0) 192 { 193 enforce(chmod(this.address_pathnul.ptr, mode) == 0, 194 "Couldn't change UnixSocket mode."); 195 } 196 197 log.info("Listening on \"{}\"", address_path); 198 } 199 catch (Exception e) 200 { 201 log.error("Unable to bind to or listen on \"{}\": {}", 202 address_path, e.message()); 203 throw e; 204 } 205 } 206 207 /*************************************************************************** 208 209 Deletes the socket file on shutdown. 210 211 ***************************************************************************/ 212 213 override public void shutdown ( ) 214 { 215 super.shutdown(); 216 217 auto log = Log.lookup("ocean.net.server.unixsocket"); 218 219 if (unlink(this.address_pathnul.ptr)) 220 { 221 char[0x100] buf; 222 auto msgnul = strerror_r(errno, buf.ptr, buf.length); 223 log.error("Unable to delete socket \"{}\": {}", 224 this.address_pathnul[0 .. strlen(this.address_pathnul.ptr)], 225 msgnul[0 .. strlen(msgnul)]); 226 } 227 else 228 { 229 log.info("Deleted socket \"{}\"", this.address_pathnul[0 .. strlen(this.address_pathnul.ptr)]); 230 } 231 } 232 }