xref: /openbsd-src/usr.sbin/iscsid/session.c (revision 91f110e064cd7c194e59e019b83bb7496c1c84d4)
1 /*	$OpenBSD: session.c,v 1.4 2011/05/04 21:00:04 claudio Exp $ */
2 
3 /*
4  * Copyright (c) 2011 Claudio Jeker <claudio@openbsd.org>
5  *
6  * Permission to use, copy, modify, and distribute this software for any
7  * purpose with or without fee is hereby granted, provided that the above
8  * copyright notice and this permission notice appear in all copies.
9  *
10  * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
11  * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
12  * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
13  * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
14  * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
15  * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
16  * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
17  */
18 
19 #include <sys/types.h>
20 #include <sys/ioctl.h>
21 #include <sys/queue.h>
22 #include <sys/socket.h>
23 #include <sys/uio.h>
24 
25 #include <scsi/iscsi.h>
26 #include <scsi/scsi_all.h>
27 #include <dev/vscsivar.h>
28 
29 #include <event.h>
30 #include <stdio.h>
31 #include <stdlib.h>
32 #include <string.h>
33 #include <unistd.h>
34 
35 #include "iscsid.h"
36 #include "log.h"
37 
38 void	session_fsm_callback(int, short, void *);
39 int	sess_do_start(struct session *, struct sessev *);
40 int	sess_do_conn_loggedin(struct session *, struct sessev *);
41 int	sess_do_conn_fail(struct session *, struct sessev *);
42 int	sess_do_conn_closed(struct session *, struct sessev *);
43 int	sess_do_down(struct session *, struct sessev *);
44 
45 const char *sess_state(int);
46 const char *sess_event(enum s_event);
47 
48 struct session *
49 session_find(struct initiator *i, char *name)
50 {
51 	struct session *s;
52 
53 	TAILQ_FOREACH(s, &i->sessions, entry) {
54 		if (strcmp(s->config.SessionName, name) == 0)
55 			return s;
56 	}
57 	return NULL;
58 }
59 
60 struct session *
61 session_new(struct initiator *i, u_int8_t st)
62 {
63 	struct session *s;
64 
65 	if (!(s = calloc(1, sizeof(*s))))
66 		return NULL;
67 
68 	/* use the same qualifier unless there is a conflict */
69 	s->isid_base = i->config.isid_base;
70 	s->isid_qual = i->config.isid_qual;
71 	s->cmdseqnum = arc4random();
72 	s->itt = arc4random();
73 	s->initiator = i;
74 	s->state = SESS_INIT;
75 	s->mine = initiator_sess_defaults;
76 	s->mine.MaxConnections = s->config.MaxConnections;
77 	s->his = iscsi_sess_defaults;
78 	s->active = iscsi_sess_defaults;
79 
80 	if (st == SESSION_TYPE_DISCOVERY)
81 		s->target = 0;
82 	else
83 		s->target = s->initiator->target++;
84 
85 	TAILQ_INSERT_HEAD(&i->sessions, s, entry);
86 	TAILQ_INIT(&s->connections);
87 	TAILQ_INIT(&s->tasks);
88 	SIMPLEQ_INIT(&s->fsmq);
89 	evtimer_set(&s->fsm_ev, session_fsm_callback, s);
90 
91 	return s;
92 }
93 
94 void
95 session_cleanup(struct session *s)
96 {
97 	struct connection *c;
98 
99 	taskq_cleanup(&s->tasks);
100 
101 	while ((c = TAILQ_FIRST(&s->connections)) != NULL)
102 		conn_free(c);
103 
104 	free(s->config.TargetName);
105 	free(s->config.InitiatorName);
106 	free(s);
107 }
108 
109 int
110 session_shutdown(struct session *s)
111 {
112 	log_debug("session[%s] going down", s->config.SessionName);
113 
114 	s->action = SESS_ACT_DOWN;
115 	if (s->state & (SESS_INIT | SESS_FREE | SESS_DOWN)) {
116 		struct connection *c;
117 		while ((c = TAILQ_FIRST(&s->connections)) != NULL)
118 			conn_free(c);
119 		return 0;
120 	}
121 
122 	/* cleanup task queue and issue a logout */
123 	taskq_cleanup(&s->tasks);
124 	initiator_logout(s, NULL, ISCSI_LOGOUT_CLOSE_SESS);
125 
126 	return 1;
127 }
128 
129 void
130 session_config(struct session *s, struct session_config *sc)
131 {
132 	if (s->config.TargetName)
133 		free(s->config.TargetName);
134 	s->config.TargetName = NULL;
135 	if (s->config.InitiatorName)
136 		free(s->config.InitiatorName);
137 	s->config.InitiatorName = NULL;
138 
139 	s->config = *sc;
140 
141 	if (sc->TargetName) {
142 		s->config.TargetName = strdup(sc->TargetName);
143 		if (s->config.TargetName == NULL)
144 			fatal("strdup");
145 	}
146 	if (sc->InitiatorName) {
147 		s->config.InitiatorName = strdup(sc->InitiatorName);
148 		if (s->config.InitiatorName == NULL)
149 			fatal("strdup");
150 	} else
151 		s->config.InitiatorName = default_initiator_name();
152 }
153 
154 void
155 session_task_issue(struct session *s, struct task *t)
156 {
157 	TAILQ_INSERT_TAIL(&s->tasks, t, entry);
158 	session_schedule(s);
159 }
160 
161 void
162 session_logout_issue(struct session *s, struct task *t)
163 {
164 	struct connection *c, *rc = NULL;
165 
166 	/* find first free session or first available session */
167 	TAILQ_FOREACH(c, &s->connections, entry) {
168 		if (conn_task_ready(c)) {
169 			conn_fsm(c, CONN_EV_LOGOUT);
170 			conn_task_issue(c, t);
171 			return;
172 		}
173 		if (c->state & CONN_RUNNING)
174 			rc = c;
175 	}
176 
177 	if (rc) {
178 		conn_fsm(rc, CONN_EV_LOGOUT);
179 		conn_task_issue(rc, t);
180 		return;
181 	}
182 
183 	/* XXX must open new connection, gulp */
184 	fatalx("session_logout_issue needs more work");
185 }
186 
187 void
188 session_schedule(struct session *s)
189 {
190 	struct task *t = TAILQ_FIRST(&s->tasks);
191 	struct connection *c;
192 
193 	if (!t)
194 		return;
195 
196 	/* XXX IMMEDIATE TASK NEED SPECIAL HANDLING !!!! */
197 
198 	/* wake up a idle connection or a not busy one */
199 	/* XXX this needs more work as it makes the daemon go wrooOOOMM */
200 	TAILQ_FOREACH(c, &s->connections, entry)
201 		if (conn_task_ready(c)) {
202 			TAILQ_REMOVE(&s->tasks, t, entry);
203 			conn_task_issue(c, t);
204 			return;
205 		}
206 }
207 
208 /*
209  * The session FSM runs from a callback so that the connection FSM can finish.
210  */
211 void
212 session_fsm(struct session *s, enum s_event ev, struct connection *c)
213 {
214 	struct timeval tv;
215 	struct sessev *sev;
216 
217 	if ((sev = malloc(sizeof(*sev))) == NULL)
218 		fatal("session_fsm");
219 	sev->conn = c;
220 	sev->event = ev;
221 	SIMPLEQ_INSERT_TAIL(&s->fsmq, sev, entry);
222 
223 	timerclear(&tv);
224 	if (evtimer_add(&s->fsm_ev, &tv) == -1)
225 		fatal("session_fsm");
226 }
227 
228 struct {
229 	int		state;
230 	enum s_event	event;
231 	int		(*action)(struct session *, struct sessev *);
232 } s_fsm[] = {
233 	{ SESS_INIT, SESS_EV_START, sess_do_start },
234 	{ SESS_FREE, SESS_EV_CONN_LOGGED_IN, sess_do_conn_loggedin },
235 	{ SESS_LOGGED_IN, SESS_EV_CONN_LOGGED_IN, sess_do_conn_loggedin },
236 	{ SESS_RUNNING, SESS_EV_CONN_FAIL, sess_do_conn_fail },
237 	{ SESS_RUNNING, SESS_EV_CONN_CLOSED, sess_do_conn_closed },
238 	{ SESS_RUNNING, SESS_EV_CLOSED, sess_do_down },
239 	{ 0, 0, NULL }
240 };
241 
242 /* ARGSUSED */
243 void
244 session_fsm_callback(int fd, short event, void *arg)
245 {
246 	struct session *s = arg;
247 	struct sessev *sev;
248 	int	i, ns;
249 
250 	while ((sev = SIMPLEQ_FIRST(&s->fsmq))) {
251 		SIMPLEQ_REMOVE_HEAD(&s->fsmq, entry);
252 		for (i = 0; s_fsm[i].action != NULL; i++) {
253 			if (s->state & s_fsm[i].state &&
254 			    sev->event == s_fsm[i].event) {
255 				log_debug("sess_fsm[%s]: %s ev %s",
256 				    s->config.SessionName, sess_state(s->state),
257 				    sess_event(sev->event));
258 				ns = s_fsm[i].action(s, sev);
259 				if (ns == -1)
260 					/* XXX better please */
261 					fatalx("sess_fsm: action failed");
262 				log_debug("sess_fsm[%s]: new state %s",
263 				    s->config.SessionName,
264 				    sess_state(ns));
265 				s->state = ns;
266 				break;
267 			}
268 		}
269 		if (s_fsm[i].action == NULL) {
270 			log_warnx("sess_fsm[%s]: unhandled state transition "
271 			    "[%s, %s]", s->config.SessionName,
272 			    sess_state(s->state), sess_event(sev->event));
273 			fatalx("bjork bjork bjork");
274 		}
275 		free(sev);
276 	}
277 }
278 
279 int
280 sess_do_start(struct session *s, struct sessev *sev)
281 {
282 	log_debug("new connection to %s",
283 	    log_sockaddr(&s->config.connection.TargetAddr));
284 	conn_new(s, &s->config.connection);
285 
286 	return SESS_FREE;
287 }
288 
289 int
290 sess_do_conn_loggedin(struct session *s, struct sessev *sev)
291 {
292 	if (s->state & SESS_LOGGED_IN)
293 		return SESS_LOGGED_IN;
294 
295 	if (s->config.SessionType == SESSION_TYPE_DISCOVERY)
296 		initiator_discovery(s);
297 	else
298 		vscsi_event(VSCSI_REQPROBE, s->target, -1);
299 
300 	return SESS_LOGGED_IN;
301 }
302 
303 int
304 sess_do_conn_fail(struct session *s, struct sessev *sev)
305 {
306 	struct connection *c = sev->conn;
307 	int state = SESS_FREE;
308 
309 	if (sev->conn == NULL) {
310 		log_warnx("Just what do you think you're doing, Dave?");
311 		return -1;
312 	}
313 
314 	/*
315 	 * cleanup connections:
316 	 * Connections in state FREE can be removed.
317 	 * Connections in any error state will cause the session to enter
318 	 * the FAILED state. If no sessions are left and the session was
319 	 * not already FREE then explicit recovery needs to be done.
320 	 */
321 
322 	switch (c->state) {
323 	case CONN_FREE:
324 		conn_free(c);
325 		break;
326 	case CONN_CLEANUP_WAIT:
327 		break;
328 	default:
329 		log_warnx("It can only be attributable to human error.");
330 		return -1;
331 	}
332 
333 	TAILQ_FOREACH(c, &s->connections, entry) {
334 		if (c->state & CONN_FAILED) {
335 			state = SESS_FAILED;
336 			break;
337 		} else if (c->state & CONN_RUNNING)
338 			state = SESS_LOGGED_IN;
339 	}
340 
341 	return state;
342 }
343 
344 int
345 sess_do_conn_closed(struct session *s, struct sessev *sev)
346 {
347 	struct connection *c = sev->conn;
348 	int state = SESS_FREE;
349 
350 	if (c == NULL || c->state != CONN_FREE) {
351 		log_warnx("Just what do you think you're doing, Dave?");
352 		return -1;
353 	}
354 	conn_free(c);
355 
356 	TAILQ_FOREACH(c, &s->connections, entry) {
357 		if (c->state & CONN_FAILED) {
358 			state = SESS_FAILED;
359 			break;
360 		} else if (c->state & CONN_RUNNING)
361 			state = SESS_LOGGED_IN;
362 	}
363 
364 	return state;
365 }
366 
367 int
368 sess_do_down(struct session *s, struct sessev *sev)
369 {
370 	struct connection *c;
371 
372 	while ((c = TAILQ_FIRST(&s->connections)) != NULL)
373 		conn_free(c);
374 
375 	/* XXX anything else to reset to initial state? */
376 
377 	return SESS_DOWN;
378 }
379 
380 const char *
381 sess_state(int s)
382 {
383 	static char buf[15];
384 
385 	switch (s) {
386 	case SESS_INIT:
387 		return "INIT";
388 	case SESS_FREE:
389 		return "FREE";
390 	case SESS_LOGGED_IN:
391 		return "LOGGED_IN";
392 	case SESS_FAILED:
393 		return "FAILED";
394 	case SESS_DOWN:
395 		return "DOWN";
396 	default:
397 		snprintf(buf, sizeof(buf), "UKNWN %x", s);
398 		return buf;
399 	}
400 	/* NOTREACHED */
401 }
402 
403 const char *
404 sess_event(enum s_event e)
405 {
406 	static char buf[15];
407 
408 	switch (e) {
409 	case SESS_EV_START:
410 		return "start";
411 	case SESS_EV_CONN_LOGGED_IN:
412 		return "connection logged in";
413 	case SESS_EV_CONN_FAIL:
414 		return "connection fail";
415 	case SESS_EV_CONN_CLOSED:
416 		return "connection closed";
417 	case SESS_EV_CLOSED:
418 		return "session closed";
419 	case SESS_EV_FAIL:
420 		return "fail";
421 	}
422 
423 	snprintf(buf, sizeof(buf), "UKNWN %d", e);
424 	return buf;
425 }
426