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