session.c revision 1.4
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
38void	session_fsm_callback(int, short, void *);
39int	sess_do_start(struct session *, struct sessev *);
40int	sess_do_conn_loggedin(struct session *, struct sessev *);
41int	sess_do_conn_fail(struct session *, struct sessev *);
42int	sess_do_conn_closed(struct session *, struct sessev *);
43int	sess_do_down(struct session *, struct sessev *);
44
45const char *sess_state(int);
46const char *sess_event(enum s_event);
47
48struct session *
49session_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
60struct session *
61session_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
94void
95session_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
109int
110session_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
129void
130session_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
154void
155session_task_issue(struct session *s, struct task *t)
156{
157	TAILQ_INSERT_TAIL(&s->tasks, t, entry);
158	session_schedule(s);
159}
160
161void
162session_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
187void
188session_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 */
211void
212session_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
228struct {
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 */
243void
244session_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
279int
280sess_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
289int
290sess_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
303int
304sess_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
344int
345sess_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
367int
368sess_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
380const char *
381sess_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
403const char *
404sess_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