xref: /illumos-gate/usr/src/cmd/vscan/vscand/vs_eng.c (revision bea83d026ee1bd1b2a2419e1d0232f107a5d7d9b)
1 /*
2  * CDDL HEADER START
3  *
4  * The contents of this file are subject to the terms of the
5  * Common Development and Distribution License (the "License").
6  * You may not use this file except in compliance with the License.
7  *
8  * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
9  * or http://www.opensolaris.org/os/licensing.
10  * See the License for the specific language governing permissions
11  * and limitations under the License.
12  *
13  * When distributing Covered Code, include this CDDL HEADER in each
14  * file and include the License file at usr/src/OPENSOLARIS.LICENSE.
15  * If applicable, add the following below this CDDL HEADER, with the
16  * fields enclosed by brackets "[]" replaced with your own identifying
17  * information: Portions Copyright [yyyy] [name of copyright owner]
18  *
19  * CDDL HEADER END
20  */
21 /*
22  * Copyright 2008 Sun Microsystems, Inc.  All rights reserved.
23  * Use is subject to license terms.
24  */
25 
26 #pragma ident	"%Z%%M%	%I%	%E% SMI"
27 
28 /*
29  * vs_eng.c manages the vs_engines array of scan engine.
30  * Access to the array and other private data is protected by vs_eng_mutex.
31  * A caller can wait for an available engine connection on vs_eng_cv
32  *
33  */
34 
35 #include <sys/types.h>
36 #include <sys/synch.h>
37 #include <sys/socket.h>
38 #include <sys/filio.h>
39 #include <sys/ioctl.h>
40 #include <sys/debug.h>
41 #include <netinet/in.h>
42 #include <netinet/tcp.h>
43 #include <arpa/inet.h>
44 #include <unistd.h>
45 #include <stdlib.h>
46 #include <string.h>
47 #include <syslog.h>
48 #include <errno.h>
49 #include <poll.h>
50 #include <pthread.h>
51 #include <time.h>
52 
53 #include "vs_incl.h"
54 
55 
56 typedef struct vs_engine {
57 	vs_props_se_t vse_cfg;	/* host, port, maxcon */
58 	int vse_in_use;	/* # connections in use */
59 	int vse_error;
60 	vs_eng_conn_t vse_conn_root;
61 } vs_engine_t;
62 
63 static vs_engine_t vs_engines[VS_SE_MAX];
64 
65 static int vs_eng_next;		/* round-robin "finger" */
66 static int vs_eng_count;	/* how many configured engines */
67 static int vs_eng_total_maxcon;	/* total configured connections */
68 static int vs_eng_total_inuse;	/* total connections in use */
69 static int vs_eng_wait_count;	/* # threads waiting for connection */
70 
71 static pthread_mutex_t vs_eng_mutex = PTHREAD_MUTEX_INITIALIZER;
72 static pthread_cond_t vs_eng_cv;
73 static pthread_cond_t vs_eng_shutdown_cv;
74 
75 static time_t vs_eng_wait = VS_ENG_WAIT_DFLT;
76 
77 /* local functions */
78 static int vs_eng_check_errors(void);
79 static int vs_eng_find_next(int);
80 static void vs_eng_add_connection(vs_eng_conn_t *);
81 static void vs_eng_remove_connection(vs_eng_conn_t *);
82 static void vs_eng_close_connections(void);
83 static int vs_eng_compare(int, char *, int);
84 
85 
86 #ifdef FIONBIO
87 /* non-blocking connect */
88 static int nbio_connect(vs_eng_conn_t *, const struct sockaddr *, int);
89 int vs_connect_timeout = 5000; /* milliseconds */
90 #endif /* FIONBIO */
91 
92 
93 /*
94  * vs_eng_init
95  */
96 void
97 vs_eng_init()
98 {
99 	(void) pthread_cond_init(&vs_eng_cv, NULL);
100 	(void) pthread_cond_init(&vs_eng_shutdown_cv, NULL);
101 	(void) pthread_mutex_lock(&vs_eng_mutex);
102 
103 	(void) memset(vs_engines, 0,
104 	    sizeof (vs_engine_t) * VS_SE_MAX);
105 	vs_eng_total_maxcon = 0;
106 	vs_eng_total_inuse = 0;
107 	vs_eng_count = 0;
108 	vs_eng_next = 0;
109 
110 	(void) pthread_mutex_unlock(&vs_eng_mutex);
111 }
112 
113 
114 /*
115  * vs_eng_config
116  *
117  * Configure scan engine connections.
118  *
119  * If a scan engine has been reconfigured (different host or port)
120  * the scan engine's error count is reset.
121  *
122  * vs_icap_config is invoked to reset engine-specific data stored
123  * in vs_icap.
124  *
125  */
126 void
127 vs_eng_config(vs_props_all_t *config)
128 {
129 	int i;
130 	vs_props_se_t *cfg;
131 	vs_engine_t *eng;
132 
133 	(void) pthread_mutex_lock(&vs_eng_mutex);
134 
135 	vs_eng_count = 0;
136 	vs_eng_total_maxcon = 0;
137 
138 	for (i = 0; i < VS_SE_MAX; i++) {
139 		cfg = &config->va_se[i];
140 		eng = &vs_engines[i];
141 
142 		if (vs_eng_compare(i, cfg->vep_host, cfg->vep_port) != 0)
143 			eng->vse_error = 0;
144 
145 		if (cfg->vep_enable) {
146 			eng->vse_cfg = *cfg;
147 			vs_eng_total_maxcon += cfg->vep_maxconn;
148 			vs_eng_count++;
149 		} else {
150 			(void) memset(&eng->vse_cfg, 0, sizeof (vs_props_se_t));
151 		}
152 
153 		vs_icap_config(i, eng->vse_cfg.vep_host, eng->vse_cfg.vep_port);
154 	}
155 
156 	if ((vs_eng_total_maxcon <= 0) || (vs_eng_count == 0))
157 		syslog(LOG_WARNING, "Scan Engine - no engines configured");
158 
159 	(void) pthread_mutex_unlock(&vs_eng_mutex);
160 }
161 
162 
163 /*
164  * vs_eng_fini
165  *
166  * Close all scan engine connections to abort in-progress scans,
167  * and wait until all to sessions are complete, and there are no
168  * waiting threads.
169  * Set vs_eng_total_maxcon to 0 to ensure no new engine sessions
170  * can be initiated while we're waiting.
171  */
172 void
173 vs_eng_fini()
174 {
175 	(void) pthread_mutex_lock(&vs_eng_mutex);
176 
177 	vs_eng_total_maxcon = 0;
178 
179 	vs_eng_close_connections();
180 
181 	while (vs_eng_total_inuse > 0 || vs_eng_wait_count > 0)
182 		(void) pthread_cond_wait(&vs_eng_shutdown_cv, &vs_eng_mutex);
183 
184 	(void) pthread_mutex_unlock(&vs_eng_mutex);
185 	(void) pthread_cond_destroy(&vs_eng_cv);
186 	(void) pthread_cond_destroy(&vs_eng_shutdown_cv);
187 }
188 
189 
190 /*
191  * vs_eng_set_error
192  *
193  * If the engine identified in conn (host, port) matches the
194  * engine in vs_engines set or clear the error state of the
195  * engine and update the error statistics.
196  *
197  * If error == 0, clear the error state(0), else set the error
198  * state (1)
199  */
200 void
201 vs_eng_set_error(vs_eng_conn_t *conn, int error)
202 {
203 	int idx = conn->vsc_idx;
204 
205 	(void) pthread_mutex_lock(&vs_eng_mutex);
206 
207 	if (vs_eng_compare(idx, conn->vsc_host, conn->vsc_port) == 0)
208 		vs_engines[idx].vse_error = error ? 1 : 0;
209 
210 	(void) pthread_mutex_unlock(&vs_eng_mutex);
211 }
212 
213 
214 
215 /*
216  * vs_eng_get
217  * Get next available scan engine connection.
218  * If retry != 0 look for a scan engine with no errors.
219  *
220  * Returns: 0 - success
221  *         -1 - error
222  */
223 int
224 vs_eng_get(vs_eng_conn_t *conn, int retry)
225 {
226 	struct timespec tswait;
227 	int idx;
228 
229 	(void) pthread_mutex_lock(&vs_eng_mutex);
230 
231 	/*
232 	 * If no engines connections configured or
233 	 * retry and only one engine configured, give up
234 	 */
235 	if ((vs_eng_total_maxcon <= 0) || (retry && (vs_eng_count <= 1))) {
236 		(void) pthread_mutex_unlock(&vs_eng_mutex);
237 		return (-1);
238 	}
239 
240 	tswait.tv_sec = vs_eng_wait;
241 	tswait.tv_nsec = 0;
242 
243 	while ((vscand_get_state() != VS_STATE_SHUTDOWN) &&
244 	    ((idx = vs_eng_find_next(retry)) == -1)) {
245 		/* If retry and all configured engines have errors, give up */
246 		if (retry && vs_eng_check_errors()) {
247 			(void) pthread_mutex_unlock(&vs_eng_mutex);
248 			return (-1);
249 		}
250 
251 		/* wait for a connection to become available */
252 		vs_eng_wait_count++;
253 		if (pthread_cond_reltimedwait_np(&vs_eng_cv, &vs_eng_mutex,
254 		    &tswait) < 0) {
255 			syslog(LOG_WARNING, "Scan Engine "
256 			    "- timeout waiting for available engine");
257 			vs_eng_wait_count--;
258 			if (vscand_get_state() == VS_STATE_SHUTDOWN)
259 				(void) pthread_cond_signal(&vs_eng_shutdown_cv);
260 			(void) pthread_mutex_unlock(&vs_eng_mutex);
261 			return (-1);
262 		}
263 		vs_eng_wait_count--;
264 	}
265 
266 	if (vscand_get_state() == VS_STATE_SHUTDOWN) {
267 		(void) pthread_cond_signal(&vs_eng_shutdown_cv);
268 		(void) pthread_mutex_unlock(&vs_eng_mutex);
269 		return (-1);
270 	}
271 
272 	conn->vsc_idx = idx;
273 	(void) strlcpy(conn->vsc_engid,  vs_engines[idx].vse_cfg.vep_engid,
274 	    sizeof (conn->vsc_engid));
275 	(void) strlcpy(conn->vsc_host, vs_engines[idx].vse_cfg.vep_host,
276 	    sizeof (conn->vsc_host));
277 	conn->vsc_port = vs_engines[idx].vse_cfg.vep_port;
278 
279 	/* update in use counts */
280 	vs_engines[idx].vse_in_use++;
281 	vs_eng_total_inuse++;
282 
283 	/* add to connections list for engine */
284 	vs_eng_add_connection(conn);
285 
286 	/* update round-robin index */
287 	if (!retry)
288 		vs_eng_next = (idx == VS_SE_MAX) ? 0 : idx + 1;
289 
290 	(void) pthread_mutex_unlock(&vs_eng_mutex);
291 
292 	return (0);
293 }
294 
295 
296 /*
297  * vs_eng_check_errors
298  *
299  * Check if there are any engines, with maxcon > 0,
300  * which are not in error state
301  *
302  * Returns: 1 - all (valid) engines are in error state
303  *          0 - otherwise
304  */
305 static int
306 vs_eng_check_errors()
307 {
308 	int i;
309 
310 	for (i = 0; i < VS_SE_MAX; i++) {
311 		if (vs_engines[i].vse_cfg.vep_maxconn > 0 &&
312 		    (vs_engines[i].vse_error == 0))
313 			return (0);
314 	}
315 
316 	return (1);
317 }
318 
319 
320 /*
321  * vs_eng_find_next
322  *
323  * Returns: -1 no engine connections available
324  *			idx of engine to use
325  */
326 static int
327 vs_eng_find_next(int retry)
328 {
329 	int i;
330 
331 	for (i = vs_eng_next; i < VS_SE_MAX; i++) {
332 		if (vs_engines[i].vse_in_use <
333 		    vs_engines[i].vse_cfg.vep_maxconn) {
334 			if (!retry || (vs_engines[i].vse_error == 0))
335 				return (i);
336 		}
337 	}
338 
339 	for (i = 0; i < vs_eng_next; i++) {
340 		if (vs_engines[i].vse_in_use <
341 		    vs_engines[i].vse_cfg.vep_maxconn) {
342 			if (!retry || (vs_engines[i].vse_error == 0))
343 				return (i);
344 		}
345 	}
346 
347 	return (-1);
348 }
349 
350 
351 /*
352  * vs_eng_release
353  */
354 void
355 vs_eng_release(vs_eng_conn_t *conn)
356 {
357 	int idx = conn->vsc_idx;
358 
359 	/* disconnect */
360 	if (conn->vsc_sockfd != -1) {
361 		(void) close(conn->vsc_sockfd);
362 		conn->vsc_sockfd = -1;
363 	}
364 
365 	(void) pthread_mutex_lock(&vs_eng_mutex);
366 
367 	/* decrement in use counts */
368 	vs_engines[idx].vse_in_use--;
369 	vs_eng_total_inuse--;
370 
371 	/* remove from connections list for engine */
372 	vs_eng_remove_connection(conn);
373 
374 	/* wake up next thread waiting for a connection */
375 	(void) pthread_cond_signal(&vs_eng_cv);
376 
377 	/* if shutdown, send shutdown signal */
378 	if (vscand_get_state() == VS_STATE_SHUTDOWN)
379 		(void) pthread_cond_signal(&vs_eng_shutdown_cv);
380 
381 	(void) pthread_mutex_unlock(&vs_eng_mutex);
382 }
383 
384 
385 /*
386  * vs_eng_add_connection
387  * Add a connection into appropriate engine's connections list
388  */
389 static void
390 vs_eng_add_connection(vs_eng_conn_t *conn)
391 {
392 	vs_eng_conn_t *conn_root;
393 
394 	conn_root = &(vs_engines[conn->vsc_idx].vse_conn_root);
395 	conn->vsc_prev = conn_root;
396 	conn->vsc_next = conn_root->vsc_next;
397 	if (conn->vsc_next)
398 		(conn->vsc_next)->vsc_prev = conn;
399 	conn_root->vsc_next = conn;
400 }
401 
402 
403 /*
404  * vs_eng_remove_connection
405  * Remove a connection from appropriate engine's connections list
406  */
407 static void
408 vs_eng_remove_connection(vs_eng_conn_t *conn)
409 {
410 	(conn->vsc_prev)->vsc_next = conn->vsc_next;
411 	if (conn->vsc_next)
412 		(conn->vsc_next)->vsc_prev = conn->vsc_prev;
413 }
414 
415 
416 /*
417  * vs_eng_close_connections
418  * Close all open connections to abort in-progress scans.
419  */
420 static void
421 vs_eng_close_connections(void)
422 {
423 	int i;
424 	vs_eng_conn_t *conn;
425 
426 	for (i = 0; i < VS_SE_MAX; i++) {
427 		conn = vs_engines[i].vse_conn_root.vsc_next;
428 		while (conn) {
429 			(void) close(conn->vsc_sockfd);
430 			conn->vsc_sockfd = -1;
431 			conn = conn->vsc_next;
432 		}
433 	}
434 }
435 
436 
437 /*
438  * vs_eng_connect
439  * open socket connection to remote scan engine
440  */
441 int
442 vs_eng_connect(vs_eng_conn_t *conn)
443 {
444 	int rc, sock_opt, err_num;
445 	struct sockaddr_in addr;
446 	struct hostent *hp;
447 
448 	if ((conn->vsc_sockfd = socket(AF_INET, SOCK_STREAM, 0)) == -1)
449 		return (-1);
450 
451 	hp = getipnodebyname(conn->vsc_host, AF_INET, 0, &err_num);
452 	if (hp == NULL)
453 		return (-1);
454 
455 	(void) memset(&addr, 0, sizeof (addr));
456 	(void) memcpy(&addr.sin_addr, hp->h_addr, hp->h_length);
457 	addr.sin_port = htons(conn->vsc_port);
458 	addr.sin_family = hp->h_addrtype;
459 	freehostent(hp);
460 
461 #ifdef FIONBIO /* Use non-blocking mode for connect. */
462 	rc = nbio_connect(conn, (struct sockaddr *)&addr,
463 	    sizeof (struct sockaddr));
464 #else
465 	rc = connect(conn->vsc_sockfd, (struct sockaddr *)&addr,
466 	    sizeof (struct sockaddr));
467 #endif
468 
469 	sock_opt = 1;
470 
471 	if ((rc < 0) || (vscand_get_state() == VS_STATE_SHUTDOWN) ||
472 	    (setsockopt(conn->vsc_sockfd, IPPROTO_TCP, TCP_NODELAY,
473 	    &sock_opt, sizeof (sock_opt)) < 0) ||
474 	    (setsockopt(conn->vsc_sockfd, SOL_SOCKET, SO_KEEPALIVE,
475 	    &sock_opt, sizeof (sock_opt)) < 0)) {
476 		syslog(LOG_WARNING, "Scan Engine - connection error (%s:%d) %s",
477 		    conn->vsc_host, conn->vsc_port, strerror(errno));
478 		(void) close(conn->vsc_sockfd);
479 		conn->vsc_sockfd = -1;
480 		return (-1);
481 	}
482 
483 	return (0);
484 }
485 
486 
487 /*
488  * nbio_connect
489  *
490  * Attempt to do a non-blocking connect call.
491  * Wait for a maximum of "vs_connect_timeout" millisec, then check for
492  * socket error to determine if connect successful or not.
493  */
494 #ifdef FIONBIO
495 static int
496 nbio_connect(vs_eng_conn_t *conn, const struct sockaddr *sa, int sa_len)
497 {
498 	struct pollfd pfd;
499 	int nbio, rc;
500 	int soc = conn->vsc_sockfd;
501 	int error, len = sizeof (error);
502 
503 	nbio = 1;
504 	if ((ioctl(soc, FIONBIO, &nbio)) < 0)
505 		return (connect(soc, sa, sa_len));
506 
507 	if ((rc = connect(soc, sa, sa_len)) != 0) {
508 		if (errno == EINPROGRESS || errno == EINTR) {
509 			pfd.fd = soc;
510 			pfd.events = POLLOUT;
511 			pfd.revents = 0;
512 
513 			if ((rc = poll(&pfd, 1, vs_connect_timeout)) <= 0) {
514 				if (rc == 0)
515 					errno = ETIMEDOUT;
516 				rc = -1;
517 			} else {
518 				rc = getsockopt(soc, SOL_SOCKET, SO_ERROR,
519 				    &error, &len);
520 				if (rc != 0 || error != 0)
521 					rc = -1;
522 			}
523 		}
524 	}
525 
526 	nbio = 0;
527 	(void) ioctl(soc, FIONBIO, &nbio);
528 
529 	return (rc);
530 }
531 #endif
532 
533 
534 /*
535  * vs_eng_scanstamp_current
536  *
537  * Check if scanstamp matches that of ANY engine with no errors.
538  * We cannot include engines with errors as they may have been
539  * inaccessible for a long time and thus we may have an old
540  * scanstamp value for them.
541  * If a match is found the scanstamp is considered to be current
542  *
543  * returns: 1 if current, 0 otherwise
544  */
545 int
546 vs_eng_scanstamp_current(vs_scanstamp_t scanstamp)
547 {
548 	int i;
549 
550 	/* if scan stamp is null, not current */
551 	if (scanstamp[0] == '\0')
552 		return (0);
553 
554 	/* if scanstamp matches that of any enabled engine with no errors */
555 	(void) pthread_mutex_lock(&vs_eng_mutex);
556 	for (i = 0; i < VS_SE_MAX; i++) {
557 		if ((vs_engines[i].vse_cfg.vep_enable) &&
558 		    (vs_engines[i].vse_error == 0) &&
559 		    (vs_icap_compare_scanstamp(i, scanstamp) == 0))
560 			break;
561 	}
562 	(void) pthread_mutex_unlock(&vs_eng_mutex);
563 
564 	return ((i < VS_SE_MAX) ? 1 : 0);
565 }
566 
567 
568 /*
569  * vs_eng_compare
570  * compare host and port with that stored for engine idx
571  *
572  * Returns: 0 - if equal
573  */
574 static int
575 vs_eng_compare(int idx, char *host, int port)
576 {
577 	if (vs_engines[idx].vse_cfg.vep_port != port)
578 		return (-1);
579 
580 	if (strcmp(vs_engines[idx].vse_cfg.vep_host, host) != 0)
581 		return (-1);
582 
583 	return (0);
584 }
585