1 /* distcache, Distributed Session Caching technology
2 * Copyright (C) 2000-2003 Geoff Thorpe, and Cryptographic Appliances, Inc.
3 * Copyright (C) 2004 The Distcache.org project
4 *
5 * This library is free software; you can redistribute it and/or modify it under
6 * the terms of the GNU Lesser General Public License as published by the Free
7 * Software Foundation; using version 2.1 of the License. The copyright holders
8 * may elect to allow the application of later versions of the License to this
9 * software, please contact the author (geoff@distcache.org) if you wish us to
10 * review any later version released by the Free Software Foundation.
11 *
12 * This library is distributed in the hope that it will be useful, but WITHOUT
13 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
14 * FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more
15 * details.
16 *
17 * You should have received a copy of the GNU Lesser General Public License
18 * along with this library; if not, write to the Free Software Foundation, Inc.,
19 * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
20 */
21
22 #define SYS_GENERATING_EXE
23
24 #include <libsys/pre.h>
25 #include <libnal/nal.h>
26 #include <distcache/dc_server.h>
27 #include <distcache/dc_plug.h>
28 #include <distcache/dc_internal.h>
29 #include <libsys/post.h>
30
31 static const char *def_server = NULL;
32 static const unsigned int def_sessions = 512;
33 static const unsigned long def_progress = 0;
34 #ifndef WIN32
35 static const char *def_pidfile = NULL;
36 static const char *def_user = NULL;
37 static const char *def_sockowner = NULL;
38 static const char *def_sockgroup = NULL;
39 static const char *def_sockperms = NULL;
40 #endif
41
42 /* Avoid the dreaded "greater than the length `509' ISO C89 compilers are
43 * required to support" warning by splitting this into an array of strings. */
44 static const char *usage_msg[] = {
45 "",
46 "Usage: dc_server [options] where 'options' are from;",
47 #ifndef WIN32
48 " -daemon (detach and run in the background)",
49 #endif
50 " -listen <addr> (act as a server listening on address 'addr')",
51 " -sessions <num> (make the cache hold a maximum of 'num' sessions)",
52 " -progress <num> (report cache progress at least every 'num' operations)",
53 #ifndef WIN32
54 " -user <user> (run daemon as given user)",
55 " -sockowner <user> (controls ownership of unix domain listening socket)",
56 " -sockgroup <group> (controls ownership of unix domain listening socket)",
57 " -sockperms <oct> (set permissions of unix domain listening socket)",
58 " -pidfile <path> (a file to store the process ID in)",
59 " -killable (exit cleanly on a SIGUSR1 or SIGUSR2 signal)",
60 #endif
61 " -<h|help|?> (display this usage message)",
62 "\n",
63 "Eg. dc_server -listen IP:9001",
64 " will start a session cache server listening on port 9001 for all TCP/IP",
65 " interfaces.",
66 "", NULL};
67
68 #define MAX_SESSIONS DC_CACHE_MAX_SIZE
69 #define MAX_PROGRESS (unsigned long)1000000
70 #define SERVER_BUFFER_SIZE 4096
71
72 /* Prototypes used by main() */
73 static int do_server(const char *address, unsigned int max_sessions,
74 unsigned long progress, int daemon_mode,
75 const char *pidfile, int killable, const char *user,
76 const char *sockowner, const char *sockgroup,
77 const char *sockperms);
78
usage(void)79 static int usage(void)
80 {
81 const char **u = usage_msg;
82 while(*u)
83 SYS_fprintf(SYS_stderr, "%s\n", *(u++));
84 /* Return 0 because main() can use this is as a help
85 * screen which shouldn't return an "error" */
86 return 0;
87 }
88 static const char *CMD_HELP1 = "-h";
89 static const char *CMD_HELP2 = "-help";
90 static const char *CMD_HELP3 = "-?";
91 #ifndef WIN32
92 static const char *CMD_DAEMON = "-daemon";
93 static const char *CMD_USER = "-user";
94 static const char *CMD_SOCKOWNER = "-sockowner";
95 static const char *CMD_SOCKGROUP = "-sockgroup";
96 static const char *CMD_SOCKPERMS = "-sockperms";
97 static const char *CMD_PIDFILE = "-pidfile";
98 static const char *CMD_KILLABLE = "-killable";
99 #endif
100 static const char *CMD_SERVER = "-listen";
101 static const char *CMD_SESSIONS = "-sessions";
102 static const char *CMD_PROGRESS = "-progress";
103
err_noarg(const char * arg)104 static int err_noarg(const char *arg)
105 {
106 SYS_fprintf(SYS_stderr, "Error, -%s requires an argument\n", arg);
107 usage();
108 return 1;
109 }
err_badrange(const char * arg)110 static int err_badrange(const char *arg)
111 {
112 SYS_fprintf(SYS_stderr, "Error, -%s given an invalid argument\n", arg);
113 usage();
114 return 1;
115 }
err_badswitch(const char * arg)116 static int err_badswitch(const char *arg)
117 {
118 SYS_fprintf(SYS_stderr, "Error, \"%s\" not recognised\n", arg);
119 usage();
120 return 1;
121 }
122
123 /*****************/
124 /* MAIN FUNCTION */
125 /*****************/
126
127 #define ARG_INC {argc--;argv++;}
128 #define ARG_CHECK(a) \
129 if(argc < 2) \
130 return err_noarg(a); \
131 ARG_INC
132
133 /* Used to spot if we have recieved SIGUSR1 or SIGUSR2 */
134 static int got_signal = 0;
135
main(int argc,char * argv[])136 int main(int argc, char *argv[])
137 {
138 int sessions_set = 0;
139 /* Overridables */
140 unsigned int sessions = 0;
141 const char *server = def_server;
142 unsigned long progress = def_progress;
143 #ifndef WIN32
144 int daemon_mode = 0;
145 int killable = 0;
146 const char *pidfile = def_pidfile;
147 const char *user = def_user;
148 const char *sockowner = def_sockowner;
149 const char *sockgroup = def_sockgroup;
150 const char *sockperms = def_sockperms;
151 #endif
152
153 ARG_INC;
154 while(argc > 0) {
155 if((strcmp(*argv, CMD_HELP1) == 0) ||
156 (strcmp(*argv, CMD_HELP2) == 0) ||
157 (strcmp(*argv, CMD_HELP3) == 0))
158 return usage();
159 #ifndef WIN32
160 if(strcmp(*argv, CMD_DAEMON) == 0)
161 daemon_mode = 1;
162 else if(strcmp(*argv, CMD_KILLABLE) == 0) {
163 killable = 1;
164 } else if(strcmp(*argv, CMD_PIDFILE) == 0) {
165 ARG_CHECK(CMD_PIDFILE);
166 pidfile = *argv;
167 } else if(strcmp(*argv, CMD_USER) == 0) {
168 ARG_CHECK(CMD_USER);
169 user = *argv;
170 } else if(strcmp(*argv, CMD_SOCKOWNER) == 0) {
171 ARG_CHECK(CMD_SOCKOWNER);
172 sockowner = *argv;
173 } else if(strcmp(*argv, CMD_SOCKGROUP) == 0) {
174 ARG_CHECK(CMD_SOCKGROUP);
175 sockgroup = *argv;
176 } else if(strcmp(*argv, CMD_SOCKPERMS) == 0) {
177 ARG_CHECK(CMD_SOCKPERMS);
178 sockperms = *argv;
179 } else
180 #endif
181 if(strcmp(*argv, CMD_SERVER) == 0) {
182 ARG_CHECK(CMD_SERVER);
183 server = *argv;
184 } else if(strcmp(*argv, CMD_SESSIONS) == 0) {
185 ARG_CHECK(CMD_SESSIONS);
186 sessions = (unsigned int)atoi(*argv);
187 sessions_set = 1;
188 } else if(strcmp(*argv, CMD_PROGRESS) == 0) {
189 ARG_CHECK(CMD_PROGRESS);
190 progress = (unsigned long)atoi(*argv);
191 if(progress > MAX_PROGRESS)
192 return err_badrange(CMD_PROGRESS);
193 } else
194 return err_badswitch(*argv);
195 ARG_INC;
196 }
197
198 /* Scrutinise the settings */
199 if(!server) {
200 SYS_fprintf(SYS_stderr, "Error, must provide -listen\n");
201 return 1;
202 }
203 if(!sessions_set)
204 sessions = def_sessions;
205 if((sessions < 1) || (sessions > MAX_SESSIONS))
206 return err_badrange(CMD_SESSIONS);
207 if(!SYS_sigpipe_ignore()) {
208 #if SYS_DEBUG_LEVEL > 0
209 SYS_fprintf(SYS_stderr, "Error, couldn't ignore SIGPIPE\n");
210 #endif
211 return 1;
212 }
213 if(!SYS_sigusr_interrupt(&got_signal)) {
214 #if SYS_DEBUG_LEVEL > 0
215 SYS_fprintf(SYS_stderr, "Error, couldn't ignore SIGUSR[1|2]\n");
216 #endif
217 return 1;
218 }
219 return do_server(server, sessions, progress, daemon_mode, pidfile,
220 killable, user, sockowner, sockgroup, sockperms);
221 }
222
do_server(const char * address,unsigned int max_sessions,unsigned long progress,int daemon_mode,const char * pidfile,int killable,const char * user,const char * sockowner,const char * sockgroup,const char * sockperms)223 static int do_server(const char *address, unsigned int max_sessions,
224 unsigned long progress, int daemon_mode,
225 const char *pidfile, int killable, const char *user,
226 const char *sockowner, const char *sockgroup,
227 const char *sockperms)
228 {
229 int res, ret = 1;
230 struct timeval now, last_now;
231 unsigned int total = 0, tmp_total;
232 unsigned long ops = 0, tmp_ops;
233 NAL_CONNECTION *conn = NAL_CONNECTION_new();
234 NAL_ADDRESS *addr = NAL_ADDRESS_new();
235 NAL_SELECTOR *sel = NAL_SELECTOR_new();
236 NAL_LISTENER *listener = NAL_LISTENER_new();
237 DC_SERVER *server = NULL;
238
239 if(!DC_SERVER_set_default_cache() ||
240 ((server = DC_SERVER_new(max_sessions)) == NULL) ||
241 !conn || !addr || !sel || !listener) {
242 SYS_fprintf(SYS_stderr, "Error, malloc/initialisation failure\n");
243 goto err;
244 }
245 if(!NAL_ADDRESS_create(addr, address, SERVER_BUFFER_SIZE) ||
246 !NAL_ADDRESS_can_listen(addr) ||
247 !NAL_LISTENER_create(listener, addr)) {
248 SYS_fprintf(SYS_stderr, "Error, can't listen on '%s'\n",
249 address);
250 goto err;
251 }
252 #ifndef WIN32
253 if((sockowner || sockgroup) && !NAL_LISTENER_set_fs_owner(listener,
254 sockowner, sockgroup))
255 SYS_fprintf(SYS_stderr, "Warning, can't set socket ownership "
256 "to user '%s' and group '%s', continuing anyway\n",
257 sockowner ? sockowner : "(null)",
258 sockgroup ? sockgroup : "(null)");
259 if(sockperms && !NAL_LISTENER_set_fs_perms(listener, sockperms))
260 SYS_fprintf(SYS_stderr, "Warning, can't set socket permissions "
261 "to '%s', continuing anyway\n", sockperms);
262 /* If we're going daemon mode, do it now */
263 if(daemon_mode) {
264 /* working directory becomes "/" */
265 /* stdin/stdout/stderr -> /dev/null */
266 if(!SYS_daemon(0)) {
267 SYS_fprintf(SYS_stderr, "Error, couldn't detach!\n");
268 return 1;
269 }
270 }
271 /* If we're storing our pid, do it now */
272 if(pidfile) {
273 FILE *fp = fopen(pidfile, "w");
274 if(!fp) {
275 SYS_fprintf(SYS_stderr, "Error, couldn't open 'pidfile' "
276 "at '%s'.\n", pidfile);
277 return 1;
278 }
279 SYS_fprintf(fp, "%lu", (unsigned long)SYS_getpid());
280 fclose(fp);
281 }
282 if(user) {
283 if(!SYS_setuid(user)) {
284 SYS_fprintf(SYS_stderr, "Error, couldn't become user "
285 "'%s'.\n", user);
286 return 1;
287 }
288 }
289 #endif
290 /* Add the listener to the selector */
291 if(!NAL_LISTENER_add_to_selector(listener, sel)) {
292 SYS_fprintf(SYS_stderr, "Error, selector problem\n");
293 return 1;
294 }
295 /* Set "last_now" to the current-time */
296 SYS_gettime(&last_now);
297 network_loop:
298 if(NAL_LISTENER_finished(listener)) {
299 if(DC_SERVER_clients_empty(server)) {
300 /* Clean shutdown */
301 ret = 0;
302 goto err;
303 }
304 }
305 /* Automatically break every half-second. NB: we skip the select if
306 * SIGUSR1 or SIGUSR2 has arrived to improve the chances we don't
307 * needlessly wait half a second before closing down. Of course,
308 * there's still a race condition whereby we might go into the select
309 * anyway but after the signal has been handled, but the chances are
310 * much greater that the signal arrives in the logical processing above
311 * or the select itself. Anyway, this is to make administration more
312 * responsive, not to seal off any theoretical possibility of a delay
313 * in the shutdown. */
314 if(!killable || !got_signal)
315 res = NAL_SELECTOR_select(sel, 500000, 1);
316 else
317 res = -1;
318 if(res < 0) {
319 if(!killable)
320 goto network_loop;
321 if(got_signal)
322 /* We're killable and the negative return is because of
323 * a signal interruption, in this case we return
324 * main()'s version of "success". */
325 ret = 0;
326 else if(errno == EINTR) {
327 SYS_fprintf(SYS_stderr, "Error, select interrupted for unknown "
328 "signal, continuing\n");
329 goto network_loop;
330 } else
331 SYS_fprintf(SYS_stderr, "Error, select() failed\n");
332 goto err;
333 }
334 /* This entire state-machine logic will operate with one single idea of
335 * "the time". */
336 SYS_gettime(&now);
337 tmp_ops = DC_SERVER_num_operations(server);
338 if(SYS_msecs_between(&last_now, &now) < 1000) {
339 /* We try to observe a 1-second noise limit and only violate it
340 * if a "-progress" counter was specified that we've tripped. */
341 if(!progress || ((tmp_ops / progress) == (ops / progress)))
342 goto skip_totals;
343 }
344 /* It's at least a second since the last update - so now we check (a) if
345 * the numer of stored sessions has changed, and (b) if the number of
346 * cache operations (divided by 'progress') has increased. If neither,
347 * we don't create any noise. */
348 tmp_total = DC_SERVER_items_stored(server, &now);
349 if((tmp_total == total) && (!progress ||
350 ((tmp_ops / progress) == (ops / progress)))) {
351 if(res <= 0)
352 /* Total hasn't changed, and the select broke without network
353 * activity, just ignore everything and go back. */
354 goto network_loop;
355 /* The totals haven't changed, and there was network activity */
356 goto skip_totals;
357 }
358 /* Either we tripped the specified "-progress" counter, or it has been
359 * at least 1 second since we last printed something and the number of
360 * cached sessions or number of cache operations has changed. */
361 SYS_fprintf(SYS_stderr, "Info, total operations = %7lu (+ %5lu), "
362 "total sessions = %5u (%c%3u)\n", tmp_ops, tmp_ops - ops,
363 tmp_total, (tmp_total > total ? '+' :
364 (tmp_total == total ? '=' : '-')),
365 (tmp_total > total ? tmp_total - total : total - tmp_total));
366 SYS_timecpy(&last_now, &now);
367 total = tmp_total;
368 ops = tmp_ops;
369 skip_totals:
370 /* Do I/O first, in case clients are dropped making room for accepts
371 * that would otherwise fail. */
372 if(!DC_SERVER_clients_io(server, &now)) {
373 SYS_fprintf(SYS_stderr, "Error, I/O failed\n");
374 goto err;
375 }
376 /* Now handle new connections */
377 while(!NAL_LISTENER_finished(listener) &&
378 NAL_CONNECTION_accept(conn, listener)) {
379 /* New client! */
380 if(!NAL_CONNECTION_add_to_selector(conn, sel) ||
381 !DC_SERVER_new_client(server, conn,
382 DC_CLIENT_FLAG_IN_SERVER)) {
383 SYS_fprintf(SYS_stderr, "Error, accept couldn't be handled\n");
384 goto err;
385 }
386 /* 'conn' is consumed, create a new one */
387 if((conn = NAL_CONNECTION_new()) == NULL) goto err;
388 }
389 goto network_loop;
390 err:
391 if(addr) NAL_ADDRESS_free(addr);
392 if(conn) NAL_CONNECTION_free(conn);
393 if(listener) NAL_LISTENER_free(listener);
394 if(server)
395 DC_SERVER_free(server);
396 if(sel) NAL_SELECTOR_free(sel);
397 return killable;
398 }
399
400