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