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 }