xref: /netbsd-src/sbin/iscsid/iscsid_main.c (revision 946379e7b37692fc43f68eb0d1c10daa0a7f3b6c)
1 /*	$NetBSD: iscsid_main.c,v 1.9 2015/05/30 16:00:51 joerg Exp $	*/
2 
3 /*-
4  * Copyright (c) 2005,2006,2011 The NetBSD Foundation, Inc.
5  * All rights reserved.
6  *
7  * This code is derived from software contributed to The NetBSD Foundation
8  * by Wasabi Systems, Inc.
9  *
10  * Redistribution and use in source and binary forms, with or without
11  * modification, are permitted provided that the following conditions
12  * are met:
13  * 1. Redistributions of source code must retain the above copyright
14  *    notice, this list of conditions and the following disclaimer.
15  * 2. Redistributions in binary form must reproduce the above copyright
16  *    notice, this list of conditions and the following disclaimer in the
17  *    documentation and/or other materials provided with the distribution.
18  *
19  * THIS SOFTWARE IS PROVIDED BY THE NETBSD FOUNDATION, INC. AND CONTRIBUTORS
20  * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
21  * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
22  * PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL THE FOUNDATION OR CONTRIBUTORS
23  * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
24  * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
25  * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
26  * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
27  * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
28  * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
29  * POSSIBILITY OF SUCH DAMAGE.
30  */
31 
32 #include "iscsid_globals.h"
33 
34 #include <sys/types.h>
35 #include <sys/socket.h>
36 #include <sys/un.h>
37 #include <sys/sysctl.h>
38 
39 #include <ctype.h>
40 #include <err.h>
41 #include <fcntl.h>
42 
43 #define DEVICE    "/dev/iscsi0"
44 
45 /* -------------------------------------------------------------------------- */
46 
47 list_head_t list[NUM_DAEMON_LISTS];	/* the lists this daemon keeps */
48 
49 pthread_mutex_t sesslist_lock;	/* session list lock */
50 pthread_t event_thread;			/* event thread handle */
51 
52 int driver = -1;				/* the driver's file desc */
53 int client_sock;				/* the client communication socket */
54 
55 int debug_level;	/* How much info to display */
56 int nothreads;
57 
58 /*
59    To avoid memory fragmentation (and speed things up a bit), we use the
60    static bufs unless the request or response exceeds the buffer size
61    (which it normally shouldn't, assuming we don't have thousands
62    of list entries).
63 */
64 static uint8_t req_buf[REQ_BUFFER_SIZE];	/* default buffer for requests */
65 static uint8_t rsp_buf[RSP_BUFFER_SIZE];	/* default buffer for responses */
66 
67 /* -------------------------------------------------------------------------- */
68 
69 static void __dead
70 usage(void)
71 {
72 	fprintf(stderr, "Usage: %s [-d <lvl>] [-n]\n", getprogname());
73 	exit(EXIT_FAILURE);
74 }
75 
76 
77 /*
78  * create_node_name:
79  *    Create and set default node name.
80  *
81  *    Returns 0 on success, else an error code.
82  */
83 
84 static int
85 create_node_name(void)
86 {
87 	iscsi_set_node_name_parameters_t snp;
88 	uint32_t hid = 0;
89 	size_t siz;
90 	int mib[2];
91 	unsigned char *s;
92 
93 	(void) memset(&snp, 0x0, sizeof(snp));
94 	mib[0] = CTL_KERN;
95 	mib[1] = KERN_HOSTID;
96 	siz = sizeof(hid);
97 	sysctl(mib, 2, &hid, &siz, NULL, 0);
98 	mib[1] = KERN_HOSTNAME;
99 	siz = ISCSI_STRING_LENGTH - 45;
100 	sysctl(mib, 2, snp.InitiatorAlias, &siz, NULL, 0);
101 
102 	DEB(1, ("Host Name: <%s>, Host ID: %u\n", snp.InitiatorAlias, hid));
103 	if (!snp.InitiatorAlias[0]) {
104 		printf("Warning: iSCSI Node Name not set (No Host Name)!\n");
105 		return ISCSID_STATUS_NO_INITIATOR_NAME;
106 	}
107 	for (s = snp.InitiatorAlias; *s; s++)
108 		if (!isalnum((unsigned char) *s) && *s != '-' && *s != '.' && *s != ':')
109 			*s = '-';
110 	snprintf((char *)snp.InitiatorName, sizeof(snp.InitiatorName),
111 		"iqn.1994-04.org.netbsd:iscsi.%s:%u", snp.InitiatorAlias, hid);
112 
113 	ioctl(driver, ISCSI_SET_NODE_NAME, &snp);
114 	return snp.status;
115 }
116 
117 
118 /*
119  * init_daemon:
120  *    Open driver, create communication socket.
121  *
122  *    Returns:    <0 on error
123  */
124 
125 static int
126 init_daemon(void)
127 {
128 	int sock, i;
129 	struct sockaddr_un name;
130 	iscsid_request_t req;
131 
132 	if ((driver = open(DEVICE, O_RDONLY)) < 0) {
133 		perror("opening " DEVICE);
134 		return -1;
135 	}
136 
137 	sock = socket(AF_UNIX, SOCK_DGRAM, 0);
138 	if (sock < 0) {
139 		perror("opening datagram socket");
140 		return -1;
141 	}
142 
143 	name.sun_family = AF_UNIX;
144 	strlcpy(name.sun_path, ISCSID_SOCK_NAME, sizeof(name.sun_path));
145 
146 	req.request = ISCSID_DAEMON_TEST;
147 	req.parameter_length = 0;
148 
149 	i = sendto(sock, &req, sizeof(req), 0, (struct sockaddr *)(void *)&name,
150 				(socklen_t)sizeof(struct sockaddr_un));
151 	if (i == sizeof(req)) {
152 		printf("Daemon already loaded!\n");
153 		close(sock);
154 		return -1;
155 	}
156 
157 	unlink(ISCSID_SOCK_NAME);
158 	if (bind(sock, (struct sockaddr *)(void *)&name, (socklen_t)sizeof(struct sockaddr_un))) {
159 		perror("binding name to socket");
160 		return -1;
161 	}
162 
163 	for (i = 0; i < NUM_DAEMON_LISTS; i++) {
164 		TAILQ_INIT(&list[i].list);
165 		list[i].num_entries = 0;
166 	}
167 
168 	if (!nothreads && (i = pthread_mutex_init(&sesslist_lock, NULL)) != 0) {
169 		printf("Mutex init failed (%d)\n", i);
170 		close(sock);
171 		return -1;
172 	}
173 
174 	if (!register_event_handler()) {
175 		printf("Couldn't register event handler\n");
176 		close(sock);
177 		unlink(ISCSID_SOCK_NAME);
178 		if (!nothreads)
179 			pthread_mutex_destroy(&sesslist_lock);
180 		return -1;
181 	}
182 
183 	create_node_name();
184 
185 	return sock;
186 }
187 
188 
189 /*
190  * make_rsp:
191  *    Allocate a response buffer if the static buffer is insufficient, set
192  *    the response parameter length.
193  *
194  *    Parameter:
195  *          len         Response parameter size (not counting header)
196  *          prsp        Pointer to address of response buffer
197  *          prsp_temp   Will be set to TRUE if buffer was allocated, FALSE
198  *                      for static buffer.
199  *
200  *    Returns:    Pointer to response buffer, NULL if allocation failed.
201  */
202 
203 iscsid_response_t *
204 make_rsp(size_t len, iscsid_response_t ** prsp, int *prsp_temp)
205 {
206 	iscsid_response_t *rsp;
207 
208 	if ((len + sizeof(iscsid_response_t)) > RSP_BUFFER_SIZE) {
209 		if ((rsp = calloc(1, len)) == NULL) {
210 			(*prsp)->status = ISCSID_STATUS_NO_RESOURCES;
211 			return NULL;
212 		}
213 		*prsp_temp = TRUE;
214 		*prsp = rsp;
215 	} else
216 		rsp = *prsp;
217 
218 	memset (rsp, 0, len + sizeof(iscsid_response_t));
219 	rsp->parameter_length = (uint32_t)len;
220 	return rsp;
221 }
222 
223 
224 /*
225  * process_message:
226  *    minimal parameter check and dispatch for the daemon functions.
227  *
228  *    Parameter:
229  *          req         The request
230  *          prsp        Pointer to address of response buffer
231  *          prsp_temp   Will be set to TRUE if buffer was allocated, FALSE
232  *                      for static buffer.
233  */
234 
235 static void
236 process_message(iscsid_request_t *req, iscsid_response_t **prsp, int *prsp_temp)
237 {
238 	iscsid_response_t *rsp;
239 	void *p = req->parameter;
240 
241 	*prsp_temp = FALSE;
242 	*prsp = rsp = (iscsid_response_t *)(void *)rsp_buf;
243 	rsp->parameter_length = 0;
244 	rsp->status = ISCSID_STATUS_SUCCESS;
245 
246 	switch (req->request) {
247 	case ISCSID_ADD_TARGET:
248 		if (req->parameter_length < sizeof(iscsid_add_target_req_t)) {
249 			rsp->status = ISCSID_STATUS_INVALID_PARAMETER;
250 			break;
251 		}
252 		add_target((iscsid_add_target_req_t *)p, prsp, prsp_temp);
253 		break;
254 
255 	case ISCSID_ADD_PORTAL:
256 		if (req->parameter_length != sizeof(iscsid_add_portal_req_t)) {
257 			rsp->status = ISCSID_STATUS_INVALID_PARAMETER;
258 			break;
259 		}
260 		add_portal((iscsid_add_portal_req_t *)p, prsp, prsp_temp);
261 		break;
262 
263 	case ISCSID_SET_TARGET_OPTIONS:
264 		if (req->parameter_length != sizeof(iscsid_get_set_target_options_t)) {
265 			rsp->status = ISCSID_STATUS_INVALID_PARAMETER;
266 			break;
267 		}
268 		rsp->status = set_target_options((iscsid_get_set_target_options_t *)p);
269 		break;
270 
271 	case ISCSID_GET_TARGET_OPTIONS:
272 		if (req->parameter_length != sizeof(iscsid_sym_id_t)) {
273 			rsp->status = ISCSID_STATUS_INVALID_PARAMETER;
274 			break;
275 		}
276 		rsp->status = ISCSID_STATUS_NOTIMPL;
277 		break;
278 
279 	case ISCSID_SET_TARGET_AUTHENTICATION:
280 		if (req->parameter_length !=
281 			sizeof(iscsid_set_target_authentication_req_t)) {
282 			rsp->status = ISCSID_STATUS_INVALID_PARAMETER;
283 			break;
284 		}
285 		rsp->status = set_target_auth((iscsid_set_target_authentication_req_t *)p);
286 		break;
287 
288 	case ISCSID_SLP_FIND_TARGETS:
289 		rsp->status = ISCSID_STATUS_NOTIMPL;
290 		break;
291 
292 	case ISCSID_REFRESH_TARGETS:
293 		if (req->parameter_length < sizeof(iscsid_refresh_req_t)) {
294 			rsp->status = ISCSID_STATUS_INVALID_PARAMETER;
295 			break;
296 		}
297 		rsp->status = refresh_targets((iscsid_refresh_req_t *)p);
298 		break;
299 
300 	case ISCSID_REMOVE_TARGET:
301 		if (req->parameter_length != sizeof(iscsid_list_id_t)) {
302 			rsp->status = ISCSID_STATUS_INVALID_PARAMETER;
303 			break;
304 		}
305 		rsp->status = remove_target((iscsid_list_id_t *)p);
306 		break;
307 
308 	case ISCSID_SEARCH_LIST:
309 		if (req->parameter_length != sizeof(iscsid_search_list_req_t)) {
310 			rsp->status = ISCSID_STATUS_INVALID_PARAMETER;
311 			break;
312 		}
313 		search_list((iscsid_search_list_req_t *)p, prsp, prsp_temp);
314 		break;
315 
316 	case ISCSID_GET_LIST:
317 		if (req->parameter_length != sizeof(iscsid_get_list_req_t)) {
318 			rsp->status = ISCSID_STATUS_INVALID_PARAMETER;
319 			break;
320 		}
321 		get_list((iscsid_get_list_req_t *)p, prsp, prsp_temp);
322 		break;
323 
324 	case ISCSID_GET_TARGET_INFO:
325 		if (req->parameter_length != sizeof(iscsid_list_id_t)) {
326 			rsp->status = ISCSID_STATUS_INVALID_PARAMETER;
327 			break;
328 		}
329 		get_target_info((iscsid_list_id_t *)p, prsp, prsp_temp);
330 		break;
331 
332 	case ISCSID_GET_PORTAL_INFO:
333 		if (req->parameter_length != sizeof(iscsid_list_id_t)) {
334 			rsp->status = ISCSID_STATUS_INVALID_PARAMETER;
335 			break;
336 		}
337 		get_portal_info((iscsid_list_id_t *)p, prsp, prsp_temp);
338 		break;
339 
340 #ifndef ISCSI_MINIMAL
341 	case ISCSID_ADD_ISNS_SERVER:
342 		if (req->parameter_length != sizeof(iscsid_add_isns_server_req_t)) {
343 			rsp->status = ISCSID_STATUS_INVALID_PARAMETER;
344 			break;
345 		}
346 		add_isns_server((iscsid_add_isns_server_req_t *)p,
347 						prsp, prsp_temp);
348 		break;
349 
350 	case ISCSID_GET_ISNS_SERVER:
351 		if (req->parameter_length != sizeof(iscsid_sym_id_t)) {
352 			rsp->status = ISCSID_STATUS_INVALID_PARAMETER;
353 			break;
354 		}
355 		get_isns_server((iscsid_sym_id_t *)p, prsp, prsp_temp);
356 		break;
357 
358 	case ISCSID_SLP_FIND_ISNS_SERVERS:
359 		rsp->status = ISCSID_STATUS_NOTIMPL;
360 		break;
361 
362 	case ISCSID_REMOVE_ISNS_SERVER:
363 		if (req->parameter_length != sizeof(iscsid_sym_id_t)) {
364 			rsp->status = ISCSID_STATUS_INVALID_PARAMETER;
365 			break;
366 		}
367 		rsp->status = remove_isns_server((iscsid_sym_id_t *)p);
368 		break;
369 #endif
370 
371 	case ISCSID_ADD_INITIATOR_PORTAL:
372 		if (req->parameter_length != sizeof(iscsid_add_initiator_req_t)) {
373 			rsp->status = ISCSID_STATUS_INVALID_PARAMETER;
374 			break;
375 		}
376 		add_initiator_portal((iscsid_add_initiator_req_t *)p,
377 							prsp, prsp_temp);
378 		break;
379 
380 	case ISCSID_GET_INITIATOR_PORTAL:
381 		if (req->parameter_length != sizeof(iscsid_sym_id_t)) {
382 			rsp->status = ISCSID_STATUS_INVALID_PARAMETER;
383 			break;
384 		}
385 		get_initiator_portal((iscsid_sym_id_t *)p, prsp, prsp_temp);
386 		break;
387 
388 	case ISCSID_REMOVE_INITIATOR_PORTAL:
389 		if (req->parameter_length != sizeof(iscsid_sym_id_t)) {
390 			rsp->status = ISCSID_STATUS_INVALID_PARAMETER;
391 			break;
392 		}
393 		rsp->status = remove_initiator_portal((iscsid_sym_id_t *)p);
394 		break;
395 
396 	case ISCSID_LOGIN:
397 		if (req->parameter_length != sizeof(iscsid_login_req_t)) {
398 			rsp->status = ISCSID_STATUS_INVALID_PARAMETER;
399 			break;
400 		}
401 		login((iscsid_login_req_t *)p, rsp);
402 		break;
403 
404 	case ISCSID_ADD_CONNECTION:
405 		if (req->parameter_length != sizeof(iscsid_login_req_t)) {
406 			rsp->status = ISCSID_STATUS_INVALID_PARAMETER;
407 			break;
408 		}
409 		add_connection((iscsid_login_req_t *)p, rsp);
410 		break;
411 
412 	case ISCSID_LOGOUT:
413 		if (req->parameter_length != sizeof(iscsid_sym_id_t)) {
414 			rsp->status = ISCSID_STATUS_INVALID_PARAMETER;
415 			break;
416 		}
417 		rsp->status = logout((iscsid_sym_id_t *)p);
418 		break;
419 
420 	case ISCSID_REMOVE_CONNECTION:
421 		if (req->parameter_length != sizeof(iscsid_remove_connection_req_t)) {
422 			rsp->status = ISCSID_STATUS_INVALID_PARAMETER;
423 			break;
424 		}
425 		rsp->status = remove_connection((iscsid_remove_connection_req_t *)p);
426 		break;
427 
428 	case ISCSID_GET_SESSION_LIST:
429 		get_session_list(prsp, prsp_temp);
430 		break;
431 
432 	case ISCSID_GET_CONNECTION_LIST:
433 		if (req->parameter_length != sizeof(iscsid_sym_id_t)) {
434 			rsp->status = ISCSID_STATUS_INVALID_PARAMETER;
435 			break;
436 		}
437 		get_connection_list((iscsid_sym_id_t *)p, prsp, prsp_temp);
438 		break;
439 
440 	case ISCSID_GET_CONNECTION_INFO:
441 		if (req->parameter_length != sizeof(iscsid_get_connection_info_req_t)) {
442 			rsp->status = ISCSID_STATUS_INVALID_PARAMETER;
443 			break;
444 		}
445 		get_connection_info((iscsid_get_connection_info_req_t *)p,
446 							prsp, prsp_temp);
447 		break;
448 
449 	case ISCSID_SET_NODE_NAME:
450 		if (req->parameter_length != sizeof(iscsid_set_node_name_req_t)) {
451 			rsp->status = ISCSID_STATUS_INVALID_PARAMETER;
452 			break;
453 		}
454 		rsp->status = set_node_name((iscsid_set_node_name_req_t *)p);
455 		break;
456 
457 	case ISCSID_GET_VERSION:
458 		get_version(prsp, prsp_temp);
459 		break;
460 
461 	default:
462 		rsp->status = ISCSID_STATUS_INVALID_REQUEST;
463 		break;
464 	}
465 }
466 
467 
468 /*
469  * exit_daemon:
470  *    Deregister the event handler, deregister isns servers, then exit program.
471  */
472 
473 void
474 exit_daemon(void)
475 {
476 	LOCK_SESSIONS;
477 	deregister_event_handler();
478 
479 #ifndef ISCSI_MINIMAL
480 	dereg_all_isns_servers();
481 #endif
482 
483 	printf("iSCSI Daemon Exits\n");
484 	exit(0);
485 }
486 
487 
488 /*
489  * main:
490  *    init, go daemon, then loop reading requests, processing them,
491  *    and sending responses.
492  *    Stops on receiving a terminate message (no response to that one is sent),
493  *    or when an error occurs reading or writing the socket.
494  *
495  *    Parameter:  argc, argv currently ignored.
496  */
497 
498 int
499 /*ARGSUSED*/
500 main(int argc, char **argv)
501 {
502 	int req_temp, rsp_temp, c;
503 	ssize_t ret;
504 	size_t len;
505 	struct sockaddr_un from;
506 	socklen_t fromlen;
507 	iscsid_request_t *req;
508 	iscsid_response_t *rsp;
509 	struct timeval seltout = { 2, 0 };	/* 2 second poll interval */
510 	char *p;
511 
512 	while ((c = getopt(argc, argv, "d:n")) != -1)
513 		switch (c) {
514 		case 'n':
515 			nothreads++;
516 			break;
517 		case 'd':
518 			debug_level=(int)strtol(optarg, &p, 10);
519 			if (*p)
520 				errx(EXIT_FAILURE, "illegal debug level -- %s",
521 				    optarg);
522 			break;
523 		default:
524 			usage();
525 		}
526 
527 	client_sock = init_daemon();
528 	if (client_sock < 0)
529 		exit(1);
530 
531 	printf("iSCSI Daemon loaded\n");
532 
533 	if (!debug_level)
534 		daemon(0, 1);
535 
536 	if (nothreads)
537 		setsockopt(client_sock, SOL_SOCKET, SO_RCVTIMEO, &seltout,
538 		    sizeof(seltout));
539 	else {
540 		ret = pthread_create(&event_thread, NULL, event_handler, NULL);
541 		if (ret) {
542 			printf("Thread creation failed (%zd)\n", ret);
543 			close(client_sock);
544 			unlink(ISCSID_SOCK_NAME);
545 			deregister_event_handler();
546 			pthread_mutex_destroy(&sesslist_lock);
547 			return -1;
548 		}
549 	}
550 
551     /* ---------------------------------------------------------------------- */
552 
553 	for (;;) {
554 		/* First, get size of request */
555 		req = (iscsid_request_t *)(void *)req_buf;
556 		fromlen = sizeof(from);
557 		len = sizeof(iscsid_request_t);
558 
559 		if (nothreads) {
560 			do {
561 				ret = recvfrom(client_sock, req, len, MSG_PEEK |
562 				MSG_WAITALL, (struct sockaddr *)(void *)&from,
563 			    	&fromlen);
564 				if (ret == -1)
565 					event_handler(NULL);
566 			} while (ret == -1 && errno == EAGAIN);
567 		} else {
568 			do {
569 				ret = recvfrom(client_sock, req, len, MSG_PEEK |
570 				    MSG_WAITALL, (struct sockaddr *) &from,
571 				    &fromlen);
572 				if (ret == -1)
573 					event_handler(NULL);
574 			} while (ret == -1 && errno == EAGAIN);
575 		}
576 
577 		if ((size_t)ret != len) {
578 			perror("Receiving from socket");
579 			break;
580 		}
581 		DEB(98, ("Request %d, parlen %d\n",
582 				req->request, req->parameter_length));
583 
584 		len += req->parameter_length;
585 
586 		/* now that we know the size, get the buffer for it */
587 		req_temp = (len > REQ_BUFFER_SIZE);
588 
589 		if (req_temp) {
590 			req = malloc(len);
591 			if (!req) {
592 				printf("Can't alloc %zu bytes\n", len);
593 				break;
594 			}
595 		}
596 		/* read the complete request */
597 		fromlen = sizeof(from);
598 		ret = recvfrom(client_sock, req, len, MSG_WAITALL,
599 						(struct sockaddr *)(void *)&from, &fromlen);
600 		if ((size_t)ret != len) {
601 			DEBOUT(("Error receiving from socket!\n"));
602 			if (req_temp)
603 				free(req);
604 			continue;
605 		}
606 		/* terminate? then go die. */
607 		if (req->request == ISCSID_DAEMON_TERMINATE)
608 			break;
609 
610 		/* No reply required to test message */
611 		if (req->request == ISCSID_DAEMON_TEST) {
612 			if (req_temp)
613 				free(req);
614 			continue;
615 		}
616 		/* no return path? then we can't send a reply, */
617 		/* so don't process the command */
618 		if (!from.sun_path[0]) {
619 			if (req_temp)
620 				free(req);
621 			DEBOUT(("No Return Address!\n"));
622 			continue;
623 		}
624 		/* process the request */
625 		process_message(req, &rsp, &rsp_temp);
626 		if (rsp == NULL) {
627 			if (req_temp)
628 				free(req);
629 			DEBOUT(("Invalid message!\n"));
630 			continue;
631 		}
632 
633 		DEB(98, ("Sending reply: status %d, len %d\n",
634 				rsp->status, rsp->parameter_length));
635 
636 		/* send the response */
637 		len = sizeof(iscsid_response_t) + rsp->parameter_length;
638 		ret = sendto(client_sock, rsp, len, 0,
639 					(struct sockaddr *)(void *)&from, fromlen);
640 		if (len != (size_t)ret) {
641 			DEBOUT(("Error sending reply!\n"));
642 		}
643 		/* free temp buffers if we needed them */
644 		if (req_temp)
645 			free(req);
646 		if (rsp_temp)
647 			free(rsp);
648 	}
649 
650 	exit_daemon();
651 
652 	/* we never get here */
653 	return 0;
654 }
655