xref: /dragonfly/sbin/iscontrol/fsm.c (revision 6ca88057)
1 /*-
2  * Copyright (c) 2005-2008 Daniel Braniss <danny@cs.huji.ac.il>
3  * All rights reserved.
4  *
5  * Redistribution and use in source and binary forms, with or without
6  * modification, are permitted provided that the following conditions
7  * are met:
8  * 1. Redistributions of source code must retain the above copyright
9  *    notice, this list of conditions and the following disclaimer.
10  * 2. Redistributions in binary form must reproduce the above copyright
11  *    notice, this list of conditions and the following disclaimer in the
12  *    documentation and/or other materials provided with the distribution.
13  *
14  * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
15  * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
16  * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
17  * ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
18  * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
19  * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
20  * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
21  * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
22  * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
23  * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
24  * SUCH DAMAGE.
25  *
26  */
27 
28 /*
29  | $Id: fsm.c,v 2.8 2007/05/19 16:34:21 danny Exp danny $
30  */
31 
32 #include <sys/param.h>
33 #include <sys/types.h>
34 #include <sys/socket.h>
35 #include <sys/sysctl.h>
36 
37 #include <netinet/in.h>
38 #include <netinet/tcp.h>
39 #include <arpa/inet.h>
40 #include <sys/ioctl.h>
41 #include <netdb.h>
42 #include <stdlib.h>
43 #include <unistd.h>
44 #include <stdio.h>
45 #include <string.h>
46 #include <errno.h>
47 #include <fcntl.h>
48 #include <time.h>
49 #include <syslog.h>
50 #include <stdarg.h>
51 #include <camlib.h>
52 
53 #include "iscsi.h"
54 #include "iscontrol.h"
55 
56 typedef enum {
57      T1 = 1,
58      T2, /*T3,*/ T4, T5, /*T6,*/ T7, T8, T9,
59      T10, T11, T12, T13, T14, T15, T16, T18
60 } trans_t;
61 
62 /*
63  | now supports IPV6
64  | thanks to:
65  |	Hajimu UMEMOTO @ Internet Mutual Aid Society Yokohama, Japan
66  |	ume@mahoroba.org  ume@{,jp.}FreeBSD.org
67  |	http://www.imasy.org/~ume/
68  */
69 static trans_t
70 tcpConnect(isess_t *sess)
71 {
72      isc_opt_t *op = sess->op;
73      int	val, sv_errno, soc;
74      struct     addrinfo *res, *res0, hints;
75      char	pbuf[10];
76 
77      debug_called(3);
78      if(sess->flags & (SESS_RECONNECT|SESS_REDIRECT)) {
79 	  syslog(LOG_INFO, "%s", (sess->flags & SESS_RECONNECT)
80 		 ? "Reconnect": "Redirected");
81 
82 	  debug(1, "%s", (sess->flags & SESS_RECONNECT) ? "Reconnect": "Redirected");
83 	  shutdown(sess->soc, SHUT_RDWR);
84 	  //close(sess->soc);
85 	  sess->soc = -1;
86 
87 	  sess->flags &= ~SESS_CONNECTED;
88 	  if(sess->flags & SESS_REDIRECT) {
89 	       sess->redirect_cnt++;
90 	       sess->flags |= SESS_RECONNECT;
91 	  } else
92 	       sleep(2); // XXX: actually should be ?
93 #ifdef notyet
94 	  {
95 	       time_t	sec;
96 	  // make sure we are not in a loop
97 	  // XXX: this code has to be tested
98 	  sec = time(0) - sess->reconnect_time;
99 	  if(sec > (5*60)) {
100 	       // if we've been connected for more that 5 minutes
101 	       // then just reconnect
102 	       sess->reconnect_time = sec;
103 	       sess->reconnect_cnt1 = 0;
104 	  }
105 	  else {
106 	       //
107 	       sess->reconnect_cnt1++;
108 	       if((sec / sess->reconnect_cnt1) < 2) {
109 		    // if less that 2 seconds from the last reconnect
110 		    // we are most probably looping
111 		    syslog(LOG_CRIT, "too many reconnects %d", sess->reconnect_cnt1);
112 		    return 0;
113 	       }
114 	  }
115      }
116 #endif
117 	  sess->reconnect_cnt++;
118      }
119 
120      snprintf(pbuf, sizeof(pbuf), "%d", op->port);
121      memset(&hints, 0, sizeof(hints));
122      hints.ai_family	= PF_UNSPEC;
123      hints.ai_socktype	= SOCK_STREAM;
124      debug(1, "targetAddress=%s port=%d", op->targetAddress, op->port);
125      if((val = getaddrinfo(op->targetAddress, pbuf, &hints, &res0)) != 0) {
126           fprintf(stderr, "getaddrinfo(%s): %s\n", op->targetAddress, gai_strerror(val));
127           return 0;
128      }
129      sess->flags &= ~SESS_CONNECTED;
130      sv_errno = 0;
131      soc = -1;
132      for(res = res0; res; res = res->ai_next) {
133 	  soc = socket(res->ai_family, res->ai_socktype, res->ai_protocol);
134 	  if (soc == -1)
135 	       continue;
136 
137      // from Patrick.Guelat@imp.ch:
138      // iscontrol can be called without waiting for the socket entry to time out
139 	  val = 1;
140 	  if(setsockopt(soc, SOL_SOCKET, SO_REUSEADDR, &val, (socklen_t)sizeof(val)) < 0) {
141 		  fprintf(stderr, "Cannot set socket SO_REUSEADDR %d: %s\n\n",
142 				  errno, strerror(errno));
143 	  }
144 	  if(connect(soc, res->ai_addr, res->ai_addrlen) == 0)
145 	     break;
146 
147 	  sv_errno = errno;
148 	  close(soc);
149 	  soc = -1;
150      }
151      freeaddrinfo(res0);
152 
153      if(soc != -1) {
154 	  sess->soc = soc;
155 
156 	  /* Default to TCP_NODELAY to improve transfers */
157 	  if(setsockopt(sess->soc, IPPROTO_TCP, TCP_NODELAY, &val, sizeof(val)) < 0)
158 		  fprintf(stderr, "Cannot set socket NO delay option err=%d %s\n",
159 				  errno, strerror(errno));
160 
161 #if 0
162 	  struct	timeval timeout;
163 
164 	  val = 1;
165 	  if(setsockopt(sess->soc, IPPROTO_TCP, TCP_KEEPALIVE, &val, sizeof(val)) < 0)
166 	       fprintf(stderr, "Cannot set socket KEEPALIVE option err=%d %s\n",
167 		       errno, strerror(errno));
168 
169 	  if(setsockopt(sess->soc, IPPROTO_TCP, TCP_NODELAY, &val, sizeof(val)) < 0)
170 	       fprintf(stderr, "Cannot set socket NO delay option err=%d %s\n",
171 		       errno, strerror(errno));
172 
173 	  timeout.tv_sec = 10;
174 	  timeout.tv_usec = 0;
175 	  if((setsockopt(sess->soc, SOL_SOCKET, SO_SNDTIMEO, &timeout, sizeof(timeout)) < 0)
176 	     || (setsockopt(sess->soc, SOL_SOCKET, SO_RCVTIMEO, &timeout, sizeof(timeout)) < 0)) {
177 	       fprintf(stderr, "Cannot set socket timeout to %ld err=%d %s\n",
178 		       timeout.tv_sec, errno, strerror(errno));
179 	  }
180 #endif
181 #ifdef CURIOUS
182 	  {
183 	       int len = sizeof(val);
184 	       if(getsockopt(sess->soc, SOL_SOCKET, SO_SNDBUF, &val, &len) == 0)
185 		    fprintf(stderr, "was: SO_SNDBUF=%dK\n", val/1024);
186 	  }
187 #endif
188 	  if(sess->op->sockbufsize) {
189 	       val = sess->op->sockbufsize * 1024;
190 	       if((setsockopt(sess->soc, SOL_SOCKET, SO_SNDBUF, &val, sizeof(val)) < 0)
191 		  || (setsockopt(sess->soc, SOL_SOCKET, SO_RCVBUF, &val, sizeof(val)) < 0)) {
192 		    fprintf(stderr, "Cannot set socket sndbuf & rcvbuf to %d err=%d %s\n",
193 			    val, errno, strerror(errno));
194 		    return 0;
195 	       }
196 	  }
197 	  sess->flags |= SESS_CONNECTED;
198 	  return T1;
199      }
200 
201      fprintf(stderr, "errno=%d\n", sv_errno);
202      perror("connect");
203      switch(sv_errno) {
204      case ECONNREFUSED:
205      case ENETUNREACH:
206      case ETIMEDOUT:
207 	  if((sess->flags & SESS_REDIRECT) == 0) {
208 	       if(strcmp(op->targetAddress, sess->target.address) != 0) {
209 		    syslog(LOG_INFO, "reconnecting to original target address");
210 		    free(op->targetAddress);
211 		    op->targetAddress           = sess->target.address;
212 		    op->port                    = sess->target.port;
213 		    op->targetPortalGroupTag    = sess->target.pgt;
214 		    return T1;
215 	       }
216 	  }
217 	  sleep(5); // for now ...
218 	  return T1;
219      default:
220 	  return 0; // terminal error
221      }
222 }
223 
224 int
225 setOptions(isess_t *sess, int flag)
226 {
227      isc_opt_t	oop;
228      char	*sep;
229 
230      debug_called(3);
231 
232      bzero(&oop, sizeof(isc_opt_t));
233 
234      if((flag & SESS_FULLFEATURE) == 0) {
235 	  oop.initiatorName	= sess->op->initiatorName;
236 	  oop.targetAddress	= sess->op->targetAddress;
237 	  if(sess->op->targetName != 0)
238 	       oop.targetName = sess->op->targetName;
239 
240 	  oop.maxRecvDataSegmentLength = sess->op->maxRecvDataSegmentLength;
241 	  oop.maxXmitDataSegmentLength = sess->op->maxXmitDataSegmentLength; // XXX:
242 	  oop.maxBurstLength = sess->op->maxBurstLength;
243 	  oop.maxluns = sess->op->maxluns;
244      }
245      else {
246 	  /*
247 	   | turn on digestion only after login
248 	   */
249 	  if(sess->op->headerDigest != NULL) {
250 	       sep = strchr(sess->op->headerDigest, ',');
251 	       if(sep == NULL)
252 		    oop.headerDigest = sess->op->headerDigest;
253 	       debug(1, "oop.headerDigest=%s", oop.headerDigest);
254 	  }
255 	  if(sess->op->dataDigest != NULL) {
256 	       sep = strchr(sess->op->dataDigest, ',');
257 	       if(sep == NULL)
258 		    oop.dataDigest = sess->op->dataDigest;
259 	       debug(1, "oop.dataDigest=%s", oop.dataDigest);
260 	  }
261      }
262 
263      if(ioctl(sess->fd, ISCSISETOPT, &oop)) {
264 	  perror("ISCSISETOPT");
265 	  return -1;
266      }
267      return 0;
268 }
269 
270 static trans_t
271 startSession(isess_t *sess)
272 {
273 
274      int	n, fd, nfd;
275      char	*dev;
276 
277      debug_called(3);
278 
279      if((sess->flags & SESS_CONNECTED) == 0) {
280 	  return T2;
281      }
282      if(sess->fd == -1) {
283 	  fd = open(iscsidev, O_RDWR);
284 	  if(fd < 0) {
285 	       perror(iscsidev);
286 	       return 0;
287 	  }
288 	  {
289 	       // XXX: this has to go
290 	       size_t	n;
291 	       n = sizeof(sess->isid);
292 	       if(sysctlbyname("net.iscsi.isid", (void *)sess->isid, (size_t *)&n, 0, 0) != 0)
293 		    perror("sysctlbyname");
294 	  }
295 	  if(ioctl(fd, ISCSISETSES, &n)) {
296 	       perror("ISCSISETSES");
297 	       return 0;
298 	  }
299 	  sleep(1);	/* XXX temporary */
300 	  asprintf(&dev, "%s%d", iscsidev, n);
301 	  nfd = open(dev, O_RDWR);
302 	  if(nfd < 0) {
303 	       perror(dev);
304 	       free(dev);
305 	       return 0;
306 	  }
307 	  free(dev);
308 	  close(fd);
309 	  sess->fd = nfd;
310 
311 	  if(setOptions(sess, 0) != 0)
312 	       return -1;
313      }
314 
315      if(ioctl(sess->fd, ISCSISETSOC, &sess->soc)) {
316 	  perror("ISCSISETSOC");
317 	  return 0;
318      }
319 
320      return T4;
321 }
322 
323 isess_t *currsess;
324 
325 static void
326 trap(int sig)
327 {
328      syslog(LOG_NOTICE, "trapped signal %d", sig);
329      fprintf(stderr, "trapped signal %d\n", sig);
330 
331      switch(sig) {
332      case SIGHUP:
333 	  currsess->flags |= SESS_DISCONNECT;
334 	  break;
335 
336      case SIGUSR1:
337 	  currsess->flags |= SESS_RECONNECT;
338 	  break;
339 
340      case SIGINT:
341      case SIGTERM:
342      default:
343 	  return; // ignore
344      }
345 }
346 
347 static void
348 doCAM(isess_t *sess)
349 {
350      char	pathstr[1024];
351      union ccb	*ccb;
352      int	i;
353 
354      if(ioctl(sess->fd, ISCSIGETCAM, &sess->cam) != 0) {
355 	  syslog(LOG_WARNING, "ISCSIGETCAM failed: %d", errno);
356 	  return;
357      }
358      debug(2, "nluns=%d", sess->cam.target_nluns);
359      /*
360       | for now will do this for each lun ...
361       */
362      for(i = 0; i < sess->cam.target_nluns; i++) {
363 	  debug(2, "CAM path_id=%d target_id=%d target_lun=%d",
364 		sess->cam.path_id, sess->cam.target_id, sess->cam.target_lun[i]);
365 
366 	  sess->camdev = cam_open_btl(sess->cam.path_id, sess->cam.target_id,
367 				      sess->cam.target_lun[i], O_RDWR, NULL);
368 	  if(sess->camdev == NULL) {
369 	       syslog(LOG_WARNING, "%s", cam_errbuf);
370 	       debug(3, "%s", cam_errbuf);
371 	       continue;
372 	  }
373 
374 	  cam_path_string(sess->camdev, pathstr, sizeof(pathstr));
375 	  debug(2, "pathstr=%s", pathstr);
376 
377 	  ccb = cam_getccb(sess->camdev);
378 	  bzero(&(&ccb->ccb_h)[1], sizeof(struct ccb_relsim) - sizeof(struct ccb_hdr));
379 	  ccb->ccb_h.func_code = XPT_REL_SIMQ;
380 	  ccb->crs.release_flags = RELSIM_ADJUST_OPENINGS;
381 	  ccb->crs.openings = sess->op->tags;
382 
383 	  if(cam_send_ccb(sess->camdev, ccb) < 0)
384 	       syslog(LOG_WARNING, "%s", cam_errbuf);
385 	  else
386 	  if((ccb->ccb_h.status & CAM_STATUS_MASK) != CAM_REQ_CMP) {
387 	       syslog(LOG_WARNING, "XPT_REL_SIMQ CCB failed");
388 	       // cam_error_print(sess->camdev, ccb, CAM_ESF_ALL, CAM_EPF_ALL, stderr);
389 	  }
390 	  else
391 	       syslog(LOG_INFO, "%s tagged openings now %d\n", pathstr, ccb->crs.openings);
392 
393 	  cam_freeccb(ccb);
394 	  cam_close_device(sess->camdev);
395      }
396 }
397 
398 static trans_t
399 supervise(isess_t *sess)
400 {
401      int	sig, val;
402 
403      debug_called(3);
404 
405      if(strcmp(sess->op->sessionType, "Discovery") == 0) {
406 	  sess->flags |= SESS_DISCONNECT;
407 	  return T9;
408      }
409 
410      if(vflag)
411 	  printf("ready to go scsi\n");
412 
413      if(setOptions(sess, SESS_FULLFEATURE) != 0)
414 	  return 0; // failure
415 
416      if((sess->flags & SESS_FULLFEATURE) == 0) {
417 	  if(daemon(0, 1) != 0) {
418 	       perror("daemon");
419 	       exit(1);
420 	  }
421 
422 	  openlog("iscontrol", LOG_CONS|LOG_PERROR|LOG_PID|LOG_NDELAY, LOG_KERN);
423 	  syslog(LOG_INFO, "running");
424 
425 	  currsess = sess;
426 	  if(ioctl(sess->fd, ISCSISTART)) {
427 	       perror("ISCSISTART");
428 	       return -1;
429 	  }
430 	  doCAM(sess);
431 
432      }
433      else {
434 	  if(ioctl(sess->fd, ISCSIRESTART)) {
435 	       perror("ISCSIRESTART");
436 	       return -1;
437 	  }
438      }
439 
440      signal(SIGINT, trap);
441      signal(SIGHUP, trap);
442      signal(SIGTERM, trap);
443 
444      sig = SIGUSR1;
445      signal(sig, trap);
446      if(ioctl(sess->fd, ISCSISIGNAL, &sig)) {
447 	  perror("ISCSISIGNAL");
448 	  return -1;
449      }
450      sess->flags |= SESS_FULLFEATURE;
451 
452      sess->flags &= ~(SESS_REDIRECT | SESS_RECONNECT);
453      printf("iscontrol: supervise starting main loop\n");
454      /*
455       | the main loop - actually do nothing
456       | all the work is done inside the kernel
457       */
458      while((sess->flags & (SESS_REDIRECT|SESS_RECONNECT|SESS_DISCONNECT)) == 0) {
459 	  // do something?
460 	  // like sending a nop_out?
461 	  sleep(60);
462      }
463      printf("iscontrol: supervise going down\n");
464      syslog(LOG_INFO, "sess flags=%x", sess->flags);
465 
466      sig = 0;
467      if(ioctl(sess->fd, ISCSISIGNAL, &sig)) {
468 	  perror("ISCSISIGNAL");
469      }
470 
471      if(sess->flags & SESS_DISCONNECT) {
472 	  val = 0;
473 	  if(ioctl(sess->fd, ISCSISTOP, &val)) {
474 	       perror("ISCSISTOP");
475 	  }
476 	  sess->flags &= ~SESS_FULLFEATURE;
477 	  return T9;
478      }
479      else {
480 	  sess->flags |= SESS_INITIALLOGIN1;
481      }
482      return T8;
483 }
484 
485 static int
486 handledDiscoveryResp(isess_t *sess, pdu_t *pp)
487 {
488      u_char	*ptr;
489      int	len, n;
490 
491      debug_called(3);
492 
493      len = pp->ds_len;
494      ptr = pp->ds;
495      while(len > 0) {
496 	  if(*ptr != 0)
497 	       printf("%s\n", ptr);
498 	  n = strlen((char *)ptr) + 1;
499 	  len -= n;
500 	  ptr += n;
501      }
502      return 0;
503 }
504 
505 static int
506 doDiscovery(isess_t *sess)
507 {
508      pdu_t	spp;
509      text_req_t	*tp = (text_req_t *)&spp.ipdu.bhs;
510 
511      debug_called(3);
512 
513      bzero(&spp, sizeof(pdu_t));
514      tp->cmd = ISCSI_TEXT_CMD /*| 0x40 */; // because of a bug in openiscsi-target
515      tp->F = 1;
516      tp->ttt = 0xffffffff;
517      addText(&spp, "SendTargets=All");
518      return sendPDU(sess, &spp, handledDiscoveryResp);
519 }
520 
521 static trans_t
522 doLogin(isess_t *sess)
523 {
524      isc_opt_t	*op = sess->op;
525      int	status, count;
526 
527      debug_called(3);
528 
529      if(op->chapSecret == NULL && op->tgtChapSecret == NULL)
530 	  /*
531 	   | don't need any security negotiation
532 	   | or in other words: we don't have any secrets to exchange
533 	   */
534 	  sess->csg = LON_PHASE;
535      else
536 	  sess->csg = SN_PHASE;
537 
538      if(sess->tsih) {
539 	  sess->tsih = 0;	// XXX: no 'reconnect' yet
540 	  sess->flags &= ~SESS_NEGODONE; // XXX: KLUDGE
541      }
542      count = 10; // should be more than enough
543      do {
544 	  debug(3, "count=%d csg=%d", count, sess->csg);
545 	  status = loginPhase(sess);
546 	  if(count-- == 0)
547 	       // just in case we get into a loop
548 	       status = -1;
549      } while(status == 0 && (sess->csg != FF_PHASE));
550 
551      sess->flags &= ~SESS_INITIALLOGIN;
552      debug(3, "status=%d", status);
553 
554      switch(status) {
555      case 0: // all is ok ...
556 	  sess->flags |= SESS_LOGGEDIN;
557 	  if(strcmp(sess->op->sessionType, "Discovery") == 0)
558 	       doDiscovery(sess);
559 	  return T5;
560 
561      case 1:	// redirect - temporary/permanent
562 	  /*
563 	   | start from scratch?
564 	   */
565 	  sess->flags &= ~SESS_NEGODONE;
566 	  sess->flags |= (SESS_REDIRECT | SESS_INITIALLOGIN1);
567 	  syslog(LOG_DEBUG, "target sent REDIRECT");
568 	  return T7;
569 
570      case 2: // initiator terminal error
571 	  return 0;
572      case 3: // target terminal error -- could retry ...
573 	  sleep(5);
574 	  return T7; // lets try
575      default:
576 	  return 0;
577      }
578 }
579 
580 static int
581 handleLogoutResp(isess_t *sess, pdu_t *pp)
582 {
583      if(sess->flags & SESS_DISCONNECT)
584 	  return 0;
585      return T13;
586 }
587 
588 static trans_t
589 startLogout(isess_t *sess)
590 {
591      pdu_t	spp;
592      logout_req_t *p = (logout_req_t *)&spp.ipdu.bhs;
593 
594      bzero(&spp, sizeof(pdu_t));
595      p->cmd = ISCSI_LOGOUT_CMD| 0x40;
596      p->reason = BIT(7) | 0;
597      p->CID = htons(1);
598 
599      return sendPDU(sess, &spp, handleLogoutResp);
600 }
601 
602 static trans_t
603 inLogout(isess_t *sess)
604 {
605      if(sess->flags & SESS_RECONNECT)
606 	  return T18;
607      return 0;
608 }
609 
610 typedef enum {
611      S1=1, S2, S3, S4, S5, S6, S7, S8
612 } state_t;
613 
614 #if 0
615       S1: FREE
616       S2: XPT_WAIT
617       S4: IN_LOGIN
618       S5: LOGGED_IN
619       S6: IN_LOGOUT
620       S7: LOGOUT_REQUESTED
621       S8: CLEANUP_WAIT
622 
623                      -------<-------------+
624          +--------->/ S1    \<----+       |
625       T13|       +->\       /<-+   \      |
626          |      /    ---+---    \   \     |
627          |     /        |     T2 \   |    |
628          |  T8 |        |T1       |  |    |
629          |     |        |        /   |T7  |
630          |     |        |       /    |    |
631          |     |        |      /     |    |
632          |     |        V     /     /     |
633          |     |     ------- /     /      |
634          |     |    / S2    \     /       |
635          |     |    \       /    /        |
636          |     |     ---+---    /         |
637          |     |        |T4    /          |
638          |     |        V     /           | T18
639          |     |     ------- /            |
640          |     |    / S4    \             |
641          |     |    \       /             |
642          |     |     ---+---              |         T15
643          |     |        |T5      +--------+---------+
644          |     |        |       /T16+-----+------+  |
645          |     |        |      /   -+-----+--+   |  |
646          |     |        |     /   /  S7   \  |T12|  |
647          |     |        |    / +->\       /<-+   V  V
648          |     |        |   / /    -+-----       -------
649          |     |        |  / /T11   |T10        /  S8   \
650          |     |        V / /       V  +----+   \       /
651          |     |      ---+-+-      ----+--  |    -------
652          |     |     / S5    \T9  / S6    \<+    ^
653          |     +-----\       /--->\       / T14  |
654          |            -------      --+----+------+T17
655          +---------------------------+
656 #endif
657 
658 int
659 fsm(isc_opt_t *op)
660 {
661      state_t	state;
662      isess_t	*sess;
663 
664      if((sess = calloc(1, sizeof(isess_t))) == NULL) {
665 	  // boy, is this a bad start ...
666 	  fprintf(stderr, "no memory!\n");
667 	  return -1;
668      }
669 
670      state = S1;
671      sess->op = op;
672      sess->fd = -1;
673      sess->soc = -1;
674      sess->target.address = strdup(op->targetAddress);
675      sess->target.port = op->port;
676      sess->target.pgt = op->targetPortalGroupTag;
677 
678      sess->flags = SESS_INITIALLOGIN | SESS_INITIALLOGIN1;
679 
680      do {
681 	  switch(state) {
682 	  case S1:
683 	       switch(tcpConnect(sess)) {
684 	       case T1: state = S2; break;
685 	       default: state = S8; break;
686 	       }
687 	       break;
688 
689 	  case S2:
690 	       switch(startSession(sess)) {
691 	       case T2: state = S1; break;
692 	       case T4: state = S4; break;
693 	       default: state = S8; break;
694 	       }
695 	       break;
696 
697 	  case S4:
698 	       switch(doLogin(sess)) {
699 	       case T7:  state = S1; break;
700 	       case T5:  state = S5; break;
701 	       default: state = S8; break;
702 	       }
703 	       break;
704 
705 	  case S5:
706 	       switch(supervise(sess)) {
707 	       case T8:  state = S1; break;
708 	       case T9:  state = S6; break;
709 	       case T11: state = S7; break;
710 	       case T15: state = S8; break;
711 	       default: state = S8; break;
712 	       }
713 	       break;
714 
715 	  case S6:
716 	       switch(startLogout(sess)) {
717 	       case T13: state = S1; break;
718 	       case T14: state = S6; break;
719 	       case T16: state = S8; break;
720 	       default: state = S8; break;
721 	       }
722 	       break;
723 
724 	  case S7:
725 	       switch(inLogout(sess)) {
726 	       case T18: state = S1; break;
727 	       case T10: state = S6; break;
728 	       case T12: state = S7; break;
729 	       case T16: state = S8; break;
730 	       default: state = S8; break;
731 	       }
732 	       break;
733 
734 	  case S8:
735 	       // maybe do some clean up?
736 	       syslog(LOG_INFO, "terminated");
737 	       return 0;
738 	  default:
739 	       syslog(LOG_INFO, "unknown state %d", state);
740 	       return 0;
741 	  }
742      } while(1);
743 }
744