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.core.Enforce; 111 import ocean.stdc.posix.sys.un: sockaddr_un; 112 import ocean.sys.socket.UnixSocket; 113 import ocean.text.convert.Formatter; 114 import ocean.util.log.Logger; 115 116 import core.stdc.errno: errno; 117 import core.stdc.string: strerror_r, strlen; 118 import core.sys.posix.sys.socket: AF_UNIX, sockaddr; 119 import core.sys.posix.sys.stat; 120 import core.sys.posix.unistd: unlink; 121 122 123 /*************************************************************************** 124 125 '\0'-terminated socket address path 126 127 ***************************************************************************/ 128 129 private char[sockaddr_un.sun_path.length] address_pathnul; 130 131 /*************************************************************************** 132 133 Constructor. 134 135 `address_path` is a file path that serves as the Unix domain server 136 socket address. If it exists, it will be deleted and recreated. 137 138 Params: 139 address_path = the file path i.e. address of the Unix domain server 140 socket 141 epoll = the `EpollSelectDispatcher` instance to use for I/O 142 (connection handler parameter) 143 handler = Command handler. 144 mode = mode to apply after binding the socket file 145 146 Throws: 147 `Exception` if 148 - `path` is too long; `path.length` must be less than 149 `UNIX_PATH_MAX`, 150 - an error occurred creating or binding the server socket. 151 152 ***************************************************************************/ 153 154 public this ( cstring address_path, EpollSelectDispatcher epoll, 155 CommandHandlerType handler, int mode = -1 ) 156 { 157 enforce(address_path.length < sockaddr_un.sun_path.length, 158 format("Unix socket path too long: {}", address_path)); 159 160 snformat(this.address_pathnul, "{}\0", address_path); 161 162 auto log = Log.lookup("ocean.net.server.unixsocket"); 163 164 // Don't report an error if unlink() fails. In any case success or 165 // failure is solely up to the super constructor. 166 if (!unlink(this.address_pathnul.ptr)) 167 { 168 log.warn("Deleted existing socket \"{}\"", address_path); 169 } 170 171 try 172 { 173 sockaddr_un address; 174 address.sun_family = AF_UNIX; 175 address.sun_path[0 .. this.address_pathnul.length] = 176 this.address_pathnul; 177 178 // The socket should be opened with rw-rw-r-- permissions, 179 // so the owner and group could connect to it by default. 180 auto old_umask = umask(Octal!("002")); 181 scope (exit) 182 umask(old_umask); 183 184 super(cast(sockaddr*)&address, new UnixSocket, 185 epoll, handler, address_path); 186 187 if (mode >= 0) 188 { 189 enforce(chmod(this.address_pathnul.ptr, mode) == 0, 190 "Couldn't change UnixSocket mode."); 191 } 192 193 log.info("Listening on \"{}\"", address_path); 194 } 195 catch (Exception e) 196 { 197 log.error("Unable to bind to or listen on \"{}\": {}", 198 address_path, e.message()); 199 throw e; 200 } 201 } 202 203 /*************************************************************************** 204 205 Deletes the socket file on shutdown. 206 207 ***************************************************************************/ 208 209 override public void shutdown ( ) 210 { 211 super.shutdown(); 212 213 auto log = Log.lookup("ocean.net.server.unixsocket"); 214 215 if (unlink(this.address_pathnul.ptr)) 216 { 217 char[0x100] buf; 218 auto msgnul = strerror_r(errno, buf.ptr, buf.length); 219 log.error("Unable to delete socket \"{}\": {}", 220 this.address_pathnul[0 .. strlen(this.address_pathnul.ptr)], 221 msgnul[0 .. strlen(msgnul)]); 222 } 223 else 224 { 225 log.info("Deleted socket \"{}\"", this.address_pathnul[0 .. strlen(this.address_pathnul.ptr)]); 226 } 227 } 228 }