session.c revision 1.6
1/*	$OpenBSD: session.c,v 1.6 2014/04/20 20:12:31 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
76	if (st == SESSION_TYPE_DISCOVERY)
77		s->target = 0;
78	else
79		s->target = s->initiator->target++;
80
81	TAILQ_INSERT_HEAD(&i->sessions, s, entry);
82	TAILQ_INIT(&s->connections);
83	TAILQ_INIT(&s->tasks);
84	SIMPLEQ_INIT(&s->fsmq);
85	evtimer_set(&s->fsm_ev, session_fsm_callback, s);
86
87	return s;
88}
89
90void
91session_cleanup(struct session *s)
92{
93	struct connection *c;
94
95	taskq_cleanup(&s->tasks);
96
97	while ((c = TAILQ_FIRST(&s->connections)) != NULL)
98		conn_free(c);
99
100	free(s->config.TargetName);
101	free(s->config.InitiatorName);
102	free(s);
103}
104
105int
106session_shutdown(struct session *s)
107{
108	log_debug("session[%s] going down", s->config.SessionName);
109
110	s->action = SESS_ACT_DOWN;
111	if (s->state & (SESS_INIT | SESS_FREE | SESS_DOWN)) {
112		struct connection *c;
113		while ((c = TAILQ_FIRST(&s->connections)) != NULL)
114			conn_free(c);
115		return 0;
116	}
117
118	/* cleanup task queue and issue a logout */
119	taskq_cleanup(&s->tasks);
120	initiator_logout(s, NULL, ISCSI_LOGOUT_CLOSE_SESS);
121
122	return 1;
123}
124
125void
126session_config(struct session *s, struct session_config *sc)
127{
128	if (s->config.TargetName)
129		free(s->config.TargetName);
130	s->config.TargetName = NULL;
131	if (s->config.InitiatorName)
132		free(s->config.InitiatorName);
133	s->config.InitiatorName = NULL;
134
135	s->config = *sc;
136
137	if (sc->TargetName) {
138		s->config.TargetName = strdup(sc->TargetName);
139		if (s->config.TargetName == NULL)
140			fatal("strdup");
141	}
142	if (sc->InitiatorName) {
143		s->config.InitiatorName = strdup(sc->InitiatorName);
144		if (s->config.InitiatorName == NULL)
145			fatal("strdup");
146	} else
147		s->config.InitiatorName = default_initiator_name();
148}
149
150void
151session_task_issue(struct session *s, struct task *t)
152{
153	TAILQ_INSERT_TAIL(&s->tasks, t, entry);
154	session_schedule(s);
155}
156
157void
158session_logout_issue(struct session *s, struct task *t)
159{
160	struct connection *c, *rc = NULL;
161
162	/* find first free session or first available session */
163	TAILQ_FOREACH(c, &s->connections, entry) {
164		if (conn_task_ready(c)) {
165			conn_fsm(c, CONN_EV_LOGOUT);
166			conn_task_issue(c, t);
167			return;
168		}
169		if (c->state & CONN_RUNNING)
170			rc = c;
171	}
172
173	if (rc) {
174		conn_fsm(rc, CONN_EV_LOGOUT);
175		conn_task_issue(rc, t);
176		return;
177	}
178
179	/* XXX must open new connection, gulp */
180	fatalx("session_logout_issue needs more work");
181}
182
183void
184session_schedule(struct session *s)
185{
186	struct task *t = TAILQ_FIRST(&s->tasks);
187	struct connection *c;
188
189	if (!t)
190		return;
191
192	/* XXX IMMEDIATE TASK NEED SPECIAL HANDLING !!!! */
193
194	/* wake up a idle connection or a not busy one */
195	/* XXX this needs more work as it makes the daemon go wrooOOOMM */
196	TAILQ_FOREACH(c, &s->connections, entry)
197		if (conn_task_ready(c)) {
198			TAILQ_REMOVE(&s->tasks, t, entry);
199			conn_task_issue(c, t);
200			return;
201		}
202}
203
204/*
205 * The session FSM runs from a callback so that the connection FSM can finish.
206 */
207void
208session_fsm(struct session *s, enum s_event ev, struct connection *c)
209{
210	struct timeval tv;
211	struct sessev *sev;
212
213	if ((sev = malloc(sizeof(*sev))) == NULL)
214		fatal("session_fsm");
215	sev->conn = c;
216	sev->event = ev;
217	SIMPLEQ_INSERT_TAIL(&s->fsmq, sev, entry);
218
219	timerclear(&tv);
220	if (evtimer_add(&s->fsm_ev, &tv) == -1)
221		fatal("session_fsm");
222}
223
224struct {
225	int		state;
226	enum s_event	event;
227	int		(*action)(struct session *, struct sessev *);
228} s_fsm[] = {
229	{ SESS_INIT, SESS_EV_START, sess_do_start },
230	{ SESS_FREE, SESS_EV_CONN_LOGGED_IN, sess_do_conn_loggedin },
231	{ SESS_LOGGED_IN, SESS_EV_CONN_LOGGED_IN, sess_do_conn_loggedin },
232	{ SESS_RUNNING, SESS_EV_CONN_FAIL, sess_do_conn_fail },
233	{ SESS_RUNNING, SESS_EV_CONN_CLOSED, sess_do_conn_closed },
234	{ SESS_RUNNING, SESS_EV_CLOSED, sess_do_down },
235	{ 0, 0, NULL }
236};
237
238/* ARGSUSED */
239void
240session_fsm_callback(int fd, short event, void *arg)
241{
242	struct session *s = arg;
243	struct sessev *sev;
244	int	i, ns;
245
246	while ((sev = SIMPLEQ_FIRST(&s->fsmq))) {
247		SIMPLEQ_REMOVE_HEAD(&s->fsmq, entry);
248		for (i = 0; s_fsm[i].action != NULL; i++) {
249			if (s->state & s_fsm[i].state &&
250			    sev->event == s_fsm[i].event) {
251				log_debug("sess_fsm[%s]: %s ev %s",
252				    s->config.SessionName, sess_state(s->state),
253				    sess_event(sev->event));
254				ns = s_fsm[i].action(s, sev);
255				if (ns == -1)
256					/* XXX better please */
257					fatalx("sess_fsm: action failed");
258				log_debug("sess_fsm[%s]: new state %s",
259				    s->config.SessionName,
260				    sess_state(ns));
261				s->state = ns;
262				break;
263			}
264		}
265		if (s_fsm[i].action == NULL) {
266			log_warnx("sess_fsm[%s]: unhandled state transition "
267			    "[%s, %s]", s->config.SessionName,
268			    sess_state(s->state), sess_event(sev->event));
269			fatalx("bjork bjork bjork");
270		}
271		free(sev);
272	}
273}
274
275int
276sess_do_start(struct session *s, struct sessev *sev)
277{
278	log_debug("new connection to %s",
279	    log_sockaddr(&s->config.connection.TargetAddr));
280
281	/* initialize the session params */
282	s->mine = initiator_sess_defaults;
283	s->his = iscsi_sess_defaults;
284	s->active = iscsi_sess_defaults;
285
286	if (s->config.MaxConnections)
287		s->mine.MaxConnections = s->config.MaxConnections;
288
289	conn_new(s, &s->config.connection);
290
291	return SESS_FREE;
292}
293
294int
295sess_do_conn_loggedin(struct session *s, struct sessev *sev)
296{
297	if (s->state & SESS_LOGGED_IN)
298		return SESS_LOGGED_IN;
299
300	if (s->config.SessionType == SESSION_TYPE_DISCOVERY) {
301		initiator_discovery(s);
302		return SESS_LOGGED_IN;
303	}
304
305	iscsi_merge_sess_params(&s->active, &s->mine, &s->his);
306	vscsi_event(VSCSI_REQPROBE, s->target, -1);
307
308	return SESS_LOGGED_IN;
309}
310
311int
312sess_do_conn_fail(struct session *s, struct sessev *sev)
313{
314	struct connection *c = sev->conn;
315	int state = SESS_FREE;
316
317	if (sev->conn == NULL) {
318		log_warnx("Just what do you think you're doing, Dave?");
319		return -1;
320	}
321
322	/*
323	 * cleanup connections:
324	 * Connections in state FREE can be removed.
325	 * Connections in any error state will cause the session to enter
326	 * the FAILED state. If no sessions are left and the session was
327	 * not already FREE then explicit recovery needs to be done.
328	 */
329
330	switch (c->state) {
331	case CONN_FREE:
332		conn_free(c);
333		break;
334	case CONN_CLEANUP_WAIT:
335		break;
336	default:
337		log_warnx("It can only be attributable to human error.");
338		return -1;
339	}
340
341	TAILQ_FOREACH(c, &s->connections, entry) {
342		if (c->state & CONN_FAILED) {
343			state = SESS_FAILED;
344			break;
345		} else if (c->state & CONN_RUNNING)
346			state = SESS_LOGGED_IN;
347	}
348
349	return state;
350}
351
352int
353sess_do_conn_closed(struct session *s, struct sessev *sev)
354{
355	struct connection *c = sev->conn;
356	int state = SESS_FREE;
357
358	if (c == NULL || c->state != CONN_FREE) {
359		log_warnx("Just what do you think you're doing, Dave?");
360		return -1;
361	}
362	conn_free(c);
363
364	TAILQ_FOREACH(c, &s->connections, entry) {
365		if (c->state & CONN_FAILED) {
366			state = SESS_FAILED;
367			break;
368		} else if (c->state & CONN_RUNNING)
369			state = SESS_LOGGED_IN;
370	}
371
372	return state;
373}
374
375int
376sess_do_down(struct session *s, struct sessev *sev)
377{
378	struct connection *c;
379
380	while ((c = TAILQ_FIRST(&s->connections)) != NULL)
381		conn_free(c);
382
383	/* XXX anything else to reset to initial state? */
384
385	return SESS_DOWN;
386}
387
388const char *
389sess_state(int s)
390{
391	static char buf[15];
392
393	switch (s) {
394	case SESS_INIT:
395		return "INIT";
396	case SESS_FREE:
397		return "FREE";
398	case SESS_LOGGED_IN:
399		return "LOGGED_IN";
400	case SESS_FAILED:
401		return "FAILED";
402	case SESS_DOWN:
403		return "DOWN";
404	default:
405		snprintf(buf, sizeof(buf), "UKNWN %x", s);
406		return buf;
407	}
408	/* NOTREACHED */
409}
410
411const char *
412sess_event(enum s_event e)
413{
414	static char buf[15];
415
416	switch (e) {
417	case SESS_EV_START:
418		return "start";
419	case SESS_EV_CONN_LOGGED_IN:
420		return "connection logged in";
421	case SESS_EV_CONN_FAIL:
422		return "connection fail";
423	case SESS_EV_CONN_CLOSED:
424		return "connection closed";
425	case SESS_EV_CLOSED:
426		return "session closed";
427	case SESS_EV_FAIL:
428		return "fail";
429	}
430
431	snprintf(buf, sizeof(buf), "UKNWN %d", e);
432	return buf;
433}
434