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 }