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 }