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