summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--usr.sbin/iscsid/Makefile18
-rw-r--r--usr.sbin/iscsid/connection.c379
-rw-r--r--usr.sbin/iscsid/control.c327
-rw-r--r--usr.sbin/iscsid/initiator.c409
-rw-r--r--usr.sbin/iscsid/iscsid.c226
-rw-r--r--usr.sbin/iscsid/iscsid.h297
-rw-r--r--usr.sbin/iscsid/log.c225
-rw-r--r--usr.sbin/iscsid/log.h35
-rw-r--r--usr.sbin/iscsid/pdu.c371
-rw-r--r--usr.sbin/iscsid/task.c124
-rw-r--r--usr.sbin/iscsid/util.c143
-rw-r--r--usr.sbin/iscsid/vscsi.c286
12 files changed, 2840 insertions, 0 deletions
diff --git a/usr.sbin/iscsid/Makefile b/usr.sbin/iscsid/Makefile
new file mode 100644
index 00000000000..e51fae3a33f
--- /dev/null
+++ b/usr.sbin/iscsid/Makefile
@@ -0,0 +1,18 @@
+# $OpenBSD: Makefile,v 1.1 2010/09/24 09:43:19 claudio Exp $
+
+PROG= iscsid
+SRCS= connection.c control.c initiator.c iscsid.c log.c pdu.c task.c \
+ util.c vscsi.c
+
+#MAN= iscsid.8
+
+DEBUG=-g
+CFLAGS+= -Wall
+CFLAGS+= -Wstrict-prototypes -Wmissing-prototypes
+CFLAGS+= -Wmissing-declarations
+CFLAGS+= -Wshadow -Wpointer-arith -Wcast-qual
+CFLAGS+= -Wsign-compare
+LDADD+= -levent
+DPADD+= ${LIBEVENT}
+
+.include <bsd.prog.mk>
diff --git a/usr.sbin/iscsid/connection.c b/usr.sbin/iscsid/connection.c
new file mode 100644
index 00000000000..3bf6bb653be
--- /dev/null
+++ b/usr.sbin/iscsid/connection.c
@@ -0,0 +1,379 @@
+/* $OpenBSD: connection.c,v 1.1 2010/09/24 09:43:19 claudio Exp $ */
+
+/*
+ * Copyright (c) 2009 Claudio Jeker <claudio@openbsd.org>
+ *
+ * Permission to use, copy, modify, and distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+
+#include <sys/types.h>
+#include <sys/ioctl.h>
+#include <sys/queue.h>
+#include <sys/socket.h>
+#include <sys/uio.h>
+
+#include <netinet/in.h>
+
+#include <scsi/iscsi.h>
+#include <scsi/scsi_all.h>
+#include <dev/vscsivar.h>
+
+#include <errno.h>
+#include <event.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <unistd.h>
+
+#include "iscsid.h"
+#include "log.h"
+
+void conn_fsm(struct connection *, enum c_event);
+void conn_dispatch(int, short, void *);
+void conn_write_dispatch(int, short, void *);
+
+int c_do_connect(struct connection *, enum c_event);
+int c_do_login(struct connection *, enum c_event);
+int c_do_loggedin(struct connection *, enum c_event);
+int c_do_logout(struct connection *, enum c_event);
+
+const char *conn_state(int);
+const char *conn_event(enum c_event);
+
+void
+conn_new(struct session *s, struct connection_config *cc)
+{
+ struct connection *c;
+
+ if (!(c = calloc(1, sizeof(*c))))
+ fatal("session_add_conn");
+
+ c->fd = -1;
+ c->state = CONN_FREE;
+ c->session = s;
+ c->cid = arc4random();
+ c->config = *cc;
+ TAILQ_INIT(&c->pdu_w);
+ TAILQ_INIT(&c->tasks);
+ TAILQ_INSERT_TAIL(&s->connections, c, entry);
+
+ if (pdu_readbuf_set(&c->prbuf, PDU_READ_SIZE)) {
+ log_warn("conn_new");
+ conn_free(c);
+ return;
+ }
+
+ /* create socket */
+ c->fd = socket(c->config.TargetAddr.ss_family, SOCK_STREAM, 0);
+ if (c->fd == -1) {
+ log_warn("conn_new: socket");
+ conn_free(c);
+ return;
+ }
+ if (socket_setblockmode(c->fd, 1)) {
+ log_warn("conn_new: socket_setblockmode");
+ conn_free(c);
+ return;
+ }
+
+ event_set(&c->ev, c->fd, EV_READ|EV_PERSIST, conn_dispatch, c);
+ event_set(&c->wev, c->fd, EV_WRITE, conn_write_dispatch, c);
+ event_add(&c->ev, NULL);
+
+ conn_fsm(c, CONN_EV_CONNECT);
+}
+
+void
+conn_free(struct connection *c)
+{
+ pdu_readbuf_free(&c->prbuf);
+ pdu_free_queue(&c->pdu_w);
+
+ event_del(&c->ev);
+ event_del(&c->wev);
+ close(c->fd);
+
+ TAILQ_REMOVE(&c->session->connections, c, entry);
+ free(c);
+}
+
+void
+conn_dispatch(int fd, short event, void *arg)
+{
+ struct connection *c = arg;
+ ssize_t n;
+
+ if (!(event & EV_READ)) {
+ log_debug("spurious read call");
+ return;
+ }
+ if ((n = pdu_read(c)) == -1) {
+ conn_fail(c);
+ return;
+ }
+ if (n == 0) { /* connection closed */
+ conn_fsm(c, CONN_EV_CLOSE);
+ return;
+ }
+
+ pdu_parse(c);
+}
+
+void
+conn_write_dispatch(int fd, short event, void *arg)
+{
+ struct connection *c = arg;
+ ssize_t n;
+ int error;
+ socklen_t len;
+
+ if (!(event & EV_WRITE)) {
+ log_debug("spurious write call");
+ return;
+ }
+
+ switch (c->state) {
+ case CONN_XPT_WAIT:
+ len = sizeof(error);
+ if (getsockopt(c->fd, SOL_SOCKET, SO_ERROR,
+ &error, &len) == -1 || (errno = error)) {
+ log_warn("cwd connect(%s)",
+ log_sockaddr(&c->config.TargetAddr));
+ conn_fail(c);
+ return;
+ }
+ conn_fsm(c, CONN_EV_CONNECTED);
+ break;
+ default:
+ if ((n = pdu_write(c)) == -1) {
+ conn_fail(c);
+ return;
+ }
+ if (n == 0) { /* connection closed */
+ conn_fsm(c, CONN_EV_CLOSE);
+ return;
+ }
+
+ /* check if there is more to send */
+ if (pdu_pending(c))
+ event_add(&c->wev, NULL);
+ }
+}
+
+void
+conn_close(struct connection *c)
+{
+ conn_fsm(c, CONN_EV_CLOSE);
+}
+
+void
+conn_fail(struct connection *c)
+{
+ conn_fsm(c, CONN_EV_FAIL);
+}
+
+void
+conn_loggedin(struct connection *c)
+{
+ if (c->session->config.SessionType == SESSION_TYPE_DISCOVERY)
+ conn_fsm(c, CONN_EV_DISCOVERY);
+ else
+ conn_fsm(c, CONN_EV_LOGGED_IN);
+}
+
+int
+conn_task_issue(struct connection *c, struct task *t)
+{
+ /* XXX need to verify that we're in the right state for the task */
+
+log_debug("conn_task_issue");
+ if (!TAILQ_EMPTY(&c->tasks))
+ return 0;
+
+ TAILQ_INSERT_TAIL(&c->tasks, t, entry);
+ conn_task_schedule(c);
+ return 1;
+}
+
+void
+conn_task_schedule(struct connection *c)
+{
+ struct task *t = TAILQ_FIRST(&c->tasks);
+ struct pdu *p, *np;
+
+ if (!t) {
+ log_debug("conn_task_schedule: task is hiding");
+ return;
+ }
+
+ /* move pdus to the write queue */
+ for (p = TAILQ_FIRST(&t->sendq); p != NULL; p = np) {
+ np = TAILQ_NEXT(p, entry);
+ TAILQ_REMOVE(&t->sendq, p, entry);
+ conn_pdu_write(c, p); /* maybe inline ? */
+ }
+}
+
+void
+conn_pdu_write(struct connection *c, struct pdu *p)
+{
+ struct iscsi_pdu *ipdu;
+
+/* XXX I GUESS THIS SHOULD BE MOVED TO PDU SOMEHOW... */
+ ipdu = pdu_getbuf(p, NULL, PDU_HEADER);
+ switch (ISCSI_PDU_OPCODE(ipdu->opcode)) {
+ case ISCSI_OP_TASK_REQUEST:
+ ipdu->expstatsn = ntohl(c->expstatsn);
+ break;
+ }
+
+ TAILQ_INSERT_TAIL(&c->pdu_w, p, entry);
+ event_add(&c->wev, NULL);
+}
+
+/* connection state machine more or less as specified in the RFC */
+struct {
+ int state;
+ enum c_event event;
+ int (*action)(struct connection *, enum c_event);
+} fsm[] = {
+ { CONN_FREE, CONN_EV_CONNECT, c_do_connect },
+ { CONN_XPT_WAIT, CONN_EV_CONNECTED, c_do_login },
+ { CONN_IN_LOGIN, CONN_EV_LOGGED_IN, c_do_loggedin },
+ { CONN_IN_LOGIN, CONN_EV_DISCOVERY, c_do_loggedin },
+ { CONN_LOGGED_IN, CONN_EV_CLOSE, c_do_logout },
+ { 0, 0, NULL }
+};
+
+void
+conn_fsm(struct connection *c, enum c_event event)
+{
+ int i, ns;
+
+ for (i = 0; fsm[i].action != NULL; i++) {
+ if (c->state & fsm[i].state && event == fsm[i].event) {
+ ns = fsm[i].action(c, event);
+ log_debug("conn_fsm: %s ev %s -> %s",
+ conn_state(c->state), conn_event(event),
+ conn_state(ns));
+ if (ns == -1)
+ /* XXX better please */
+ fatalx("conn_fsm: action failed");
+ c->state = ns;
+ return;
+ }
+ }
+ log_warnx("conn_fsm: unhandled state transition [%s, %s]",
+ conn_state(c->state), conn_event(event));
+ fatalx("bork bork bork");
+}
+
+int
+c_do_connect(struct connection *c, enum c_event ev)
+{
+ if (c->fd == -1)
+ fatalx("c_do_connect, lost socket");
+
+ if (connect(c->fd, (struct sockaddr *)&c->config.TargetAddr,
+ c->config.TargetAddr.ss_len) == -1) {
+ if (errno == EINPROGRESS) {
+ event_add(&c->wev, NULL);
+ return (CONN_XPT_WAIT);
+ } else {
+ log_warn("connect(%s)",
+ log_sockaddr(&c->config.TargetAddr));
+ return (-1);
+ }
+ }
+ /* move forward */
+ return (c_do_login(c, CONN_EV_CONNECTED));
+}
+
+int
+c_do_login(struct connection *c, enum c_event ev)
+{
+ /* start a login session and hope for the best ... */
+ initiator_login(c);
+ return (CONN_IN_LOGIN);
+}
+
+int
+c_do_loggedin(struct connection *c, enum c_event ev)
+{
+ if (ev == CONN_EV_LOGGED_IN)
+ vscsi_event(VSCSI_REQPROBE, c->session->target, 0);
+ else
+ initiator_discovery(c->session);
+ return (CONN_LOGGED_IN);
+}
+
+int
+c_do_logout(struct connection *c, enum c_event ev)
+{
+ /* do full logout */
+ return (CONN_FREE);
+}
+
+const char *
+conn_state(int s)
+{
+ static char buf[15];
+
+ switch (s) {
+ case CONN_FREE:
+ return "FREE";
+ case CONN_XPT_WAIT:
+ return "XPT_WAIT";
+ case CONN_XPT_UP:
+ return "XPT_UP";
+ case CONN_IN_LOGIN:
+ return "IN_LOGIN";
+ case CONN_LOGGED_IN:
+ return "LOGGED_IN";
+ case CONN_IN_LOGOUT:
+ return "IN_LOGOUT";
+ case CONN_LOGOUT_REQ:
+ return "LOGOUT_REQ";
+ case CONN_CLEANUP_WAIT:
+ return "CLEANUP_WAIT";
+ case CONN_IN_CLEANUP:
+ return "IN_CLEANUP";
+ default:
+ snprintf(buf, sizeof(buf), "UKNWN %x", s);
+ return buf;
+ }
+ /* NOTREACHED */
+}
+
+const char *
+conn_event(enum c_event e)
+{
+ static char buf[15];
+
+ switch (e) {
+ case CONN_EV_FAIL:
+ return "fail";
+ case CONN_EV_CONNECT:
+ return "connect";
+ case CONN_EV_CONNECTED:
+ return "connected";
+ case CONN_EV_LOGGED_IN:
+ return "logged in";
+ case CONN_EV_DISCOVERY:
+ return "discovery";
+ case CONN_EV_CLOSE:
+ return "close";
+ }
+
+ snprintf(buf, sizeof(buf), "UKNWN %d", e);
+ return buf;
+}
diff --git a/usr.sbin/iscsid/control.c b/usr.sbin/iscsid/control.c
new file mode 100644
index 00000000000..e95b1a45e81
--- /dev/null
+++ b/usr.sbin/iscsid/control.c
@@ -0,0 +1,327 @@
+/* $OpenBSD: control.c,v 1.1 2010/09/24 09:43:19 claudio Exp $ */
+
+/*
+ * Copyright (c) 2010 Claudio Jeker <claudio@openbsd.org>
+ * Copyright (c) 2003, 2004 Henning Brauer <henning@openbsd.org>
+ *
+ * Permission to use, copy, modify, and distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+
+#include <sys/types.h>
+#include <sys/queue.h>
+#include <sys/stat.h>
+#include <sys/socket.h>
+#include <sys/uio.h>
+#include <sys/un.h>
+#include <errno.h>
+#include <event.h>
+#include <fcntl.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+
+#include "iscsid.h"
+#include "log.h"
+
+struct control {
+ TAILQ_ENTRY(control) entry;
+ struct event ev;
+ struct pduq channel;
+ int fd;
+} *control_state;
+
+TAILQ_HEAD(, control) controls;
+
+#define CONTROL_BACKLOG 5
+
+void control_accept(int, short, void *);
+void control_close(struct control *);
+void control_dispatch(int, short, void *);
+struct pdu *control_getpdu(char *, size_t);
+
+int
+control_init(char *path)
+{
+ struct sockaddr_un sun;
+ int fd;
+ mode_t old_umask;
+
+ if ((control_state = calloc(1, sizeof(*control_state))) == NULL) {
+ log_warn("control_init: calloc");
+ return (-1);
+ }
+
+ if ((fd = socket(AF_UNIX, SOCK_STREAM, 0)) == -1) {
+ log_warn("control_init: socket");
+ return (-1);
+ }
+
+ bzero(&sun, sizeof(sun));
+ sun.sun_family = AF_UNIX;
+ strlcpy(sun.sun_path, path, sizeof(sun.sun_path));
+
+ if (unlink(path) == -1)
+ if (errno != ENOENT) {
+ log_warn("control_init: unlink %s", path);
+ close(fd);
+ return (-1);
+ }
+
+ old_umask = umask(S_IXUSR|S_IXGRP|S_IWOTH|S_IROTH|S_IXOTH);
+ if (bind(fd, (struct sockaddr *)&sun, sizeof(sun)) == -1) {
+ log_warn("control_init: bind: %s", path);
+ close(fd);
+ umask(old_umask);
+ return (-1);
+ }
+ umask(old_umask);
+
+ if (chmod(path, S_IRUSR|S_IWUSR|S_IRGRP|S_IWGRP) == -1) {
+ log_warn("control_init: chmod");
+ close(fd);
+ (void)unlink(path);
+ return (-1);
+ }
+
+ socket_setblockmode(fd, 1);
+ control_state->fd = fd;
+ TAILQ_INIT(&control_state->channel);
+ TAILQ_INIT(&controls);
+
+ return (0);
+}
+
+void
+control_cleanup(char *path)
+{
+ struct control *c;
+
+ if (path)
+ unlink(path);
+
+ while ((c = TAILQ_FIRST(&controls)) != NULL) {
+ TAILQ_REMOVE(&controls, c, entry);
+ control_close(c);
+ }
+ control_close(control_state);
+}
+
+int
+control_listen(void)
+{
+ if (listen(control_state->fd, CONTROL_BACKLOG) == -1) {
+ log_warn("control_listen: listen");
+ return (-1);
+ }
+
+ event_set(&control_state->ev, control_state->fd, EV_READ | EV_PERSIST,
+ control_accept, NULL);
+ event_add(&control_state->ev, NULL);
+
+ return (0);
+}
+
+/* ARGSUSED */
+void
+control_accept(int listenfd, short event, void *bula)
+{
+ int connfd;
+ socklen_t len;
+ struct sockaddr_un sun;
+ struct control *c;
+
+ len = sizeof(sun);
+ if ((connfd = accept(listenfd,
+ (struct sockaddr *)&sun, &len)) == -1) {
+ if (errno != EWOULDBLOCK && errno != EINTR)
+ log_warn("control_accept");
+ return;
+ }
+
+ if ((c = malloc(sizeof(struct control))) == NULL) {
+ log_warn("control_accept");
+ close(connfd);
+ return;
+ }
+
+ TAILQ_INIT(&c->channel);
+ c->fd = connfd;
+ event_set(&c->ev, connfd, EV_READ, control_dispatch, c);
+ event_add(&c->ev, NULL);
+}
+
+void
+control_close(struct control *c)
+{
+ close(c->fd);
+ event_del(&c->ev);
+ pdu_free_queue(&c->channel);
+ free(c);
+}
+
+static char cbuf[CONTROL_READ_SIZE];
+
+/* ARGSUSED */
+void
+control_dispatch(int fd, short event, void *bula)
+{
+ struct iovec iov[PDU_MAXIOV];
+ struct msghdr msg;
+ struct control *c = bula;
+ struct pdu *pdu;
+ ssize_t n;
+ unsigned int niov = 0;
+ short flags = EV_READ;
+
+ if (event & EV_TIMEOUT) {
+ log_debug("control connection (fd %d) timed out.", fd);
+ control_close(c);
+ return;
+ }
+ if (event & EV_READ) {
+ if ((n = recv(fd, cbuf, sizeof(cbuf), 0)) == -1 &&
+ !(errno == EAGAIN || errno == EINTR)) {
+ control_close(c);
+ return;
+ }
+ if (n == 0) {
+ control_close(c);
+ return;
+ }
+log_debug("control_dispatch: recv %zd bytes", n);
+ pdu = control_getpdu(cbuf, n);
+ if (!pdu) {
+ log_debug("control connection (fd %d) bad msg.", fd);
+ control_close(c);
+ return;
+ }
+ iscsid_ctrl_dispatch(c, pdu);
+ }
+ if (event & EV_WRITE) {
+ if ((pdu = TAILQ_FIRST(&c->channel)) != NULL) {
+ TAILQ_REMOVE(&c->channel, pdu, entry);
+
+ for (niov = 0; niov < PDU_MAXIOV; niov++) {
+ iov[niov].iov_base = pdu->iov[niov].iov_base;
+ iov[niov].iov_len = pdu->iov[niov].iov_len;
+ }
+ bzero(&msg, sizeof(msg));
+ msg.msg_iov = iov;
+ msg.msg_iovlen = niov;
+log_debug("control_dispatch: send %d iov", niov);
+ if (sendmsg(fd, &msg, 0) == -1 &&
+ !(errno == EAGAIN || errno == ENOBUFS)) {
+ control_close(c);
+ return;
+ }
+ }
+ }
+ if (!TAILQ_EMPTY(&c->channel))
+ flags |= EV_WRITE;
+
+ event_del(&c->ev);
+ event_set(&c->ev, fd, flags, control_dispatch, c);
+ event_add(&c->ev, NULL);
+}
+
+struct pdu *
+control_getpdu(char *buf, size_t len)
+{
+ struct pdu *p;
+ struct ctrlmsghdr *cmh;
+ void *data;
+ size_t n;
+ int i;
+
+ if (len < sizeof(*cmh))
+ return NULL;
+
+ if (!(p = pdu_new()))
+ return NULL;
+
+ n = sizeof(*cmh);
+ cmh = pdu_alloc(n);
+ bcopy(buf, cmh, n);
+ buf += n;
+ len -= n;
+
+ if (pdu_addbuf(p, cmh, n, 0)) {
+ free(cmh);
+fail:
+ pdu_free(p);
+ return NULL;
+ }
+
+ for (i = 0; i < 3; i++) {
+ n = cmh->len[i];
+ if (n == 0)
+ continue;
+ if (PDU_LEN(n) > len)
+ goto fail;
+ if (!(data = pdu_alloc(n)))
+ goto fail;
+ bcopy(buf, data, n);
+ if (pdu_addbuf(p, data, n, i + 1)) {
+ free(data);
+ goto fail;
+ }
+ buf += PDU_LEN(n);
+ len -= PDU_LEN(n);
+ }
+
+ return p;
+}
+
+int
+control_queue(void *ch, struct pdu *pdu)
+{
+ struct control *c = ch;
+
+ TAILQ_INSERT_TAIL(&c->channel, pdu, entry);
+
+ event_del(&c->ev);
+ event_set(&c->ev, c->fd, EV_READ|EV_WRITE, control_dispatch, c);
+ event_add(&c->ev, NULL);
+
+ return 0;
+}
+
+int
+control_compose(void *ch, u_int16_t type, void *buf, size_t len)
+{
+ struct pdu *pdu;
+ struct ctrlmsghdr *cmh;
+ void *ptr;
+
+ if (PDU_LEN(len) > CONTROL_READ_SIZE - PDU_LEN(sizeof(*cmh)))
+ return -1;
+ if ((pdu = pdu_new()) == NULL)
+ return -1;
+ if ((cmh = pdu_alloc(sizeof(*cmh))) == NULL)
+ goto fail;
+ bzero(cmh, sizeof(*cmh));
+ cmh->type = type;
+ cmh->len[0] = len;
+ pdu_addbuf(pdu, cmh, sizeof(*cmh), 0);
+ if (len > 0) {
+ if ((ptr = pdu_alloc(len)) == NULL)
+ goto fail;
+ bcopy(buf, ptr, len);
+ pdu_addbuf(pdu, ptr, len, 1);
+ }
+
+ return control_queue(ch, pdu);
+fail:
+ pdu_free(pdu);
+ return -1;
+}
diff --git a/usr.sbin/iscsid/initiator.c b/usr.sbin/iscsid/initiator.c
new file mode 100644
index 00000000000..a94105ea25b
--- /dev/null
+++ b/usr.sbin/iscsid/initiator.c
@@ -0,0 +1,409 @@
+/* $OpenBSD: initiator.c,v 1.1 2010/09/24 09:43:19 claudio Exp $ */
+
+/*
+ * Copyright (c) 2009 Claudio Jeker <claudio@openbsd.org>
+ *
+ * Permission to use, copy, modify, and distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+
+#include <sys/param.h>
+#include <sys/types.h>
+#include <sys/queue.h>
+#include <sys/socket.h>
+#include <sys/uio.h>
+
+#include <scsi/iscsi.h>
+
+#include <event.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+
+#include "iscsid.h"
+#include "log.h"
+
+struct initiator *initiator;
+
+struct kvp *initiator_login_kvp(struct session *);
+char *default_initiator_name(void);
+
+struct initiator *
+initiator_init(void)
+{
+ if (!(initiator = calloc(1, sizeof(*initiator))))
+ fatal("initiator_init");
+
+ initiator->config.isid_base =
+ arc4random_uniform(0xffffff) | ISCSI_ISID_RAND;
+ initiator->config.isid_qual = arc4random_uniform(0xffff);
+ TAILQ_INIT(&initiator->sessions);
+ return (initiator);
+}
+
+void
+initiator_cleanup(struct initiator *i)
+{
+ struct session *s;
+
+ while ((s = TAILQ_FIRST(&i->sessions)) != NULL) {
+ TAILQ_REMOVE(&i->sessions, s, entry);
+ session_close(s);
+ }
+ free(initiator);
+}
+
+struct session *
+initiator_t2s(u_int target)
+{
+ struct session *s;
+
+ TAILQ_FOREACH(s, &initiator->sessions, entry) {
+ if (s->target == target)
+ return s;
+ }
+ return NULL;
+}
+
+struct session *
+session_find(struct initiator *i, char *name)
+{
+ struct session *s;
+
+ TAILQ_FOREACH(s, &initiator->sessions, entry) {
+ if (strcmp(s->config.SessionName, name) == 0)
+ return s;
+ }
+ return NULL;
+}
+
+struct session *
+session_new(struct initiator *i, u_int8_t st)
+{
+ struct session *s;
+
+ if (!(s = calloc(1, sizeof(*s))))
+ return NULL;
+
+ /* use the same qualifier unless there is a conflict */
+ s->isid_base = i->config.isid_base;
+ s->isid_qual = i->config.isid_qual;
+ s->cmdseqnum = arc4random();
+ s->itt = arc4random();
+ s->initiator = i;
+ s->state = SESS_FREE;
+
+ if (st == SESSION_TYPE_DISCOVERY)
+ s->target = 0;
+ else
+ s->target = s->initiator->target++;
+
+ TAILQ_INSERT_HEAD(&i->sessions, s, entry);
+ TAILQ_INIT(&s->connections);
+ TAILQ_INIT(&s->tasks);
+
+ return s;
+}
+
+void
+session_close(struct session *s)
+{
+ struct connection *c;
+
+ while ((c = TAILQ_FIRST(&s->connections)) != NULL)
+ conn_free(c);
+
+ free(s->config.TargetName);
+ free(s->config.InitiatorName);
+ free(s);
+}
+
+void
+session_config(struct session *s, struct session_config *sc)
+{
+ if (s->config.TargetName)
+ free(s->config.TargetName);
+ s->config.TargetName = NULL;
+ if (s->config.InitiatorName)
+ free(s->config.InitiatorName);
+ s->config.InitiatorName = NULL;
+
+ s->config = *sc;
+
+ if (sc->TargetName) {
+ s->config.TargetName = strdup(sc->TargetName);
+ if (s->config.TargetName == NULL)
+ fatal("strdup");
+ }
+ if (sc->InitiatorName) {
+ s->config.InitiatorName = strdup(sc->InitiatorName);
+ if (s->config.InitiatorName == NULL)
+ fatal("strdup");
+ } else
+ s->config.InitiatorName = default_initiator_name();
+}
+
+void
+session_task_issue(struct session *s, struct task *t)
+{
+ TAILQ_INSERT_TAIL(&s->tasks, t, entry);
+ session_schedule(s);
+}
+
+void
+session_schedule(struct session *s)
+{
+ struct task *t = TAILQ_FIRST(&s->tasks);
+ struct connection *c;
+
+ if (!t)
+ return;
+
+ /* XXX IMMEDIATE TASK NEED SPECIAL HANDLING !!!! */
+
+ /* wake up a idle connection or a not busy one */
+ /* XXX this needs more work as it makes the daemon go wrooOOOMM */
+ TAILQ_REMOVE(&s->tasks, t, entry);
+ TAILQ_FOREACH(c, &s->connections, entry)
+ if (conn_task_issue(c, t))
+ return;
+ /* all connections are busy readd task to the head */
+ TAILQ_INSERT_HEAD(&s->tasks, t, entry);
+}
+
+struct task_login {
+ struct task task;
+ struct connection *c;
+ u_int16_t tsih;
+ u_int8_t stage;
+};
+
+struct pdu *initiator_login_build(struct task_login *, struct kvp *);
+void initiator_login_cb(struct connection *, void *, struct pdu *);
+
+void initiator_discovery_cb(struct connection *, void *, struct pdu *);
+struct pdu *initiator_text_build(struct task *, struct session *, struct kvp *);
+
+struct kvp *
+initiator_login_kvp(struct session *s)
+{
+ struct kvp *kvp;
+
+ if (!(kvp = calloc(4, sizeof(*kvp))))
+ return NULL;
+ kvp[0].key = "AuthMethod";
+ kvp[0].value = "None";
+ kvp[1].key = "InitiatorName";
+ kvp[1].value = s->config.InitiatorName;
+
+ if (s->config.SessionType == SESSION_TYPE_DISCOVERY) {
+ kvp[2].key = "SessionType";
+ kvp[2].value = "Discovery";
+ } else {
+ kvp[2].key = "TargetName";
+ kvp[2].value = s->config.TargetName;
+ }
+
+ return kvp;
+}
+
+void
+initiator_login(struct connection *c)
+{
+ struct task_login *tl;
+ struct pdu *p;
+ struct kvp *kvp;
+
+ if (!(tl = calloc(1, sizeof(*tl)))) {
+ log_warn("initiator_login");
+ conn_fail(c);
+ return;
+ }
+ tl->c = c;
+ tl->stage = ISCSI_LOGIN_STG_SECNEG;
+
+ if (!(kvp = initiator_login_kvp(c->session))) {
+ log_warnx("initiator_login_kvp failed");
+ free(tl);
+ conn_fail(c);
+ return;
+ }
+
+ if (!(p = initiator_login_build(tl, kvp))) {
+ log_warnx("initiator_login_build failed");
+ free(tl);
+ conn_fail(c);
+ return;
+ }
+
+ free(kvp);
+
+ task_init(&tl->task, c->session, 1, tl, initiator_login_cb);
+ task_pdu_add(&tl->task, p);
+ /* XXX this is wrong, login needs to run on a specific connection */
+ session_task_issue(c->session, &tl->task);
+}
+
+struct pdu *
+initiator_login_build(struct task_login *tl, struct kvp *kvp)
+{
+ struct pdu *p;
+ struct iscsi_pdu_login_request *lreq;
+ int n;
+
+ if (!(p = pdu_new()))
+ return NULL;
+ if (!(lreq = pdu_gethdr(p)))
+ return NULL;
+
+ lreq->opcode = ISCSI_OP_LOGIN_REQUEST | ISCSI_OP_F_IMMEDIATE;
+ if (tl->stage == ISCSI_LOGIN_STG_SECNEG)
+ lreq->flags = ISCSI_LOGIN_F_T |
+ ISCSI_LOGIN_F_CSG(ISCSI_LOGIN_STG_OPNEG) |
+ ISCSI_LOGIN_F_NSG(ISCSI_LOGIN_STG_FULL);
+ else if (tl->stage == ISCSI_LOGIN_STG_OPNEG)
+ lreq->flags = ISCSI_LOGIN_F_T |
+ ISCSI_LOGIN_F_CSG(ISCSI_LOGIN_STG_OPNEG) |
+ ISCSI_LOGIN_F_NSG(ISCSI_LOGIN_STG_FULL);
+
+ lreq->isid_base = htonl(tl->c->session->isid_base);
+ lreq->isid_qual = htons(tl->c->session->isid_qual);
+ lreq->tsih = tl->tsih;
+ lreq->cid = htons(tl->c->cid);
+ lreq->expstatsn = htonl(tl->c->expstatsn);
+
+ if ((n = text_to_pdu(kvp, p)) == -1)
+ return NULL;
+ n = htonl(n);
+ bcopy(&n, &lreq->ahslen, sizeof(n));
+
+ return p;
+}
+
+void
+initiator_login_cb(struct connection *c, void *arg, struct pdu *p)
+{
+ struct task_login *tl = arg;
+ struct iscsi_pdu_login_response *lresp;
+
+ lresp = pdu_getbuf(p, NULL, PDU_HEADER);
+ /* XXX handle packet would be great */
+ log_pdu(p, 1);
+ if (ISCSI_PDU_OPCODE(lresp->opcode) != ISCSI_OP_LOGIN_RESPONSE) {
+ log_debug("Unkown crap");
+ }
+
+ task_cleanup(&tl->task, c);
+ conn_loggedin(c);
+ free(tl);
+ pdu_free(p);
+}
+
+void
+initiator_discovery(struct session *s)
+{
+ struct task *t;
+ struct pdu *p;
+ struct kvp kvp[] = {
+ { "SendTargets", "All" },
+ { NULL, NULL }
+ };
+
+ if (!(t = calloc(1, sizeof(*t)))) {
+ log_warn("initiator_discovery");
+ return;
+ }
+
+ if (!(p = initiator_text_build(t, s, kvp))) {
+ log_warnx("initiator_text_build failed");
+ return;
+ }
+
+ task_init(t, s, 0, t, initiator_discovery_cb);
+ task_pdu_add(t, p);
+ session_task_issue(s, t);
+}
+
+struct pdu *
+initiator_text_build(struct task *t, struct session *s, struct kvp *kvp)
+{
+ struct pdu *p;
+ struct iscsi_pdu_text_request *lreq;
+ int n;
+
+ if (!(p = pdu_new()))
+ return NULL;
+ if (!(lreq = pdu_gethdr(p)))
+ return NULL;
+
+ lreq->opcode = ISCSI_OP_TEXT_REQUEST;
+ lreq->flags = ISCSI_TEXT_F_F;
+ lreq->ttt = 0xffffffff;
+
+ if ((n = text_to_pdu(kvp, p)) == -1)
+ return NULL;
+ n = htonl(n);
+ bcopy(&n, &lreq->ahslen, sizeof(n));
+
+ return p;
+}
+
+void
+initiator_discovery_cb(struct connection *c, void *arg, struct pdu *p)
+{
+ struct iscsi_pdu_text_response *lresp;
+ u_char *buf = NULL;
+ struct kvp *kvp, *k;
+ size_t n, size;
+
+ lresp = pdu_getbuf(p, NULL, PDU_HEADER);
+ switch (ISCSI_PDU_OPCODE(lresp->opcode)) {
+ case ISCSI_OP_TEXT_RESPONSE:
+ buf = pdu_getbuf(p, &n, PDU_DATA);
+ if (buf == NULL)
+ goto fail;
+ size = lresp->datalen[0] << 16 | lresp->datalen[1] << 8 |
+ lresp->datalen[2];
+ if (size > n)
+ goto fail;
+ kvp = pdu_to_text(buf, size);
+ if (kvp == NULL)
+ goto fail;
+ log_debug("ISCSI_OP_TEXT_RESPONSE");
+ for (k = kvp; k->key; k++) {
+ log_debug("%s\t=>\t%s", k->key, k->value);
+ }
+ free(kvp);
+ free(arg);
+ conn_close(c);
+ break;
+ default:
+fail:
+ conn_fail(c);
+ }
+ pdu_free(p);
+}
+
+char *
+default_initiator_name(void)
+{
+ char *s, hostname[MAXHOSTNAMELEN];
+
+ if (gethostname(hostname, sizeof(hostname)))
+ strlcpy(hostname, "initiator", sizeof(hostname));
+ if ((s = strchr(hostname, '.')))
+ *s = '\0';
+ if (asprintf(&s, "%s:%s", ISCSID_BASE_NAME, hostname) == -1)
+ return ISCSID_BASE_NAME ":initiator";
+ return s;
+}
diff --git a/usr.sbin/iscsid/iscsid.c b/usr.sbin/iscsid/iscsid.c
new file mode 100644
index 00000000000..8d226550256
--- /dev/null
+++ b/usr.sbin/iscsid/iscsid.c
@@ -0,0 +1,226 @@
+/* $OpenBSD: iscsid.c,v 1.1 2010/09/24 09:43:19 claudio Exp $ */
+
+/*
+ * Copyright (c) 2009 Claudio Jeker <claudio@openbsd.org>
+ *
+ * Permission to use, copy, modify, and distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+
+#include <sys/types.h>
+#include <sys/queue.h>
+#include <sys/socket.h>
+#include <sys/time.h>
+#include <sys/uio.h>
+
+#include <err.h>
+#include <event.h>
+#include <pwd.h>
+#include <signal.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+
+#include "iscsid.h"
+#include "log.h"
+
+void main_sig_handler(int, short, void *);
+__dead void usage(void);
+
+struct initiator *initiator;
+
+int
+main(int argc, char *argv[])
+{
+ struct event ev_sigint, ev_sigterm, ev_sighup;
+ struct passwd *pw;
+ char *ctrlsock = ISCSID_CONTROL;
+ char *vscsidev = ISCSID_DEVICE;
+ int ch, debug = 0;
+
+ log_init(1); /* log to stderr until daemonized */
+
+ while ((ch = getopt(argc, argv, "dn:s:")) != -1) {
+ switch (ch) {
+ case 'd':
+ debug = 1;
+ break;
+ case 'n':
+ vscsidev = optarg;
+ break;
+ case 's':
+ ctrlsock = optarg;
+ break;
+ default:
+ usage();
+ /* NOTREACHED */
+ }
+ }
+
+ argc -= optind;
+ argv += optind;
+
+ if (argc > 0)
+ usage();
+
+ /* check for root privileges */
+ if (geteuid())
+ errx(1, "need root privileges");
+
+ log_init(debug);
+ if (!debug)
+ daemon(1, 0);
+ log_info("startup");
+
+ event_init();
+ vscsi_open(vscsidev);
+ if (control_init(ctrlsock) == -1)
+ fatalx("control socket setup failed");
+
+ /* chroot and drop to iscsid user */
+ if ((pw = getpwnam(ISCSID_USER)) == NULL)
+ errx(1, "unknown user %s", ISCSID_USER);
+
+ if (chroot(pw->pw_dir) == -1)
+ fatal("chroot");
+ if (chdir("/") == -1)
+ fatal("chdir(\"/\")");
+ if (setgroups(1, &pw->pw_gid) ||
+ setresgid(pw->pw_gid, pw->pw_gid, pw->pw_gid) ||
+ setresuid(pw->pw_uid, pw->pw_uid, pw->pw_uid))
+ fatal("can't drop privileges");
+
+ /* setup signal handler */
+ signal_set(&ev_sigint, SIGINT, main_sig_handler, NULL);
+ signal_set(&ev_sigterm, SIGTERM, main_sig_handler, NULL);
+ signal_set(&ev_sighup, SIGHUP, main_sig_handler, NULL);
+ signal_add(&ev_sigint, NULL);
+ signal_add(&ev_sigterm, NULL);
+ signal_add(&ev_sighup, NULL);
+ signal(SIGPIPE, SIG_IGN);
+
+ if (control_listen() == -1)
+ fatalx("control socket listen failed");
+
+ initiator = initiator_init();
+
+ event_dispatch();
+
+ /* CLEANUP XXX */
+ control_cleanup(ctrlsock);
+ initiator_cleanup(initiator);
+ log_info("exiting.");
+ return 0;
+}
+
+/* ARGSUSED */
+void
+main_sig_handler(int sig, short event, void *arg)
+{
+ /* signal handler rules don't apply, libevent decouples for us */
+ switch (sig) {
+ case SIGTERM:
+ case SIGINT:
+ case SIGHUP:
+ event_loopexit(NULL);
+ break;
+ default:
+ fatalx("unexpected signal");
+ /* NOTREACHED */
+ }
+}
+
+__dead void
+usage(void)
+{
+ extern char *__progname;
+
+ fprintf(stderr, "usage: %s [-d] [-n vscsi] [-s socket]\n",
+ __progname);
+ exit(1);
+}
+
+void
+iscsid_ctrl_dispatch(void *ch, struct pdu *pdu)
+{
+ struct ctrlmsghdr *cmh;
+ struct initiator_config *ic;
+ struct session_config *sc;
+ struct session *s;
+
+ cmh = pdu_getbuf(pdu, NULL, 0);
+ if (cmh == NULL)
+ goto done;
+
+ switch (cmh->type) {
+ case CTRL_INITIATOR_CONFIG:
+log_debug("CTRL_INITIATOR_CONFIG");
+ if (cmh->len[0] != sizeof(*ic)) {
+ log_warnx("CTRL_INITIATOR_CONFIG bad size");
+ control_compose(ch, CTRL_FAILURE, NULL, 0);
+ break;
+ }
+ ic = pdu_getbuf(pdu, NULL, 1);
+ bcopy(ic, &initiator->config, sizeof(initiator->config));
+ control_compose(ch, CTRL_SUCCESS, NULL, 0);
+ break;
+ case CTRL_SESSION_CONFIG:
+log_debug("CTRL_SESSION_CONFIG");
+ if (cmh->len[0] != sizeof(*sc)) {
+ log_warnx("CTRL_INITIATOR_CONFIG bad size");
+ control_compose(ch, CTRL_FAILURE, NULL, 0);
+ break;
+ }
+ sc = pdu_getbuf(pdu, NULL, 1);
+ if (cmh->len[1])
+ sc->TargetName = pdu_getbuf(pdu, NULL, 2);
+ else if (sc->SessionType != SESSION_TYPE_DISCOVERY) {
+log_debug("no TargetName but not discovery");
+ control_compose(ch, CTRL_FAILURE, NULL, 0);
+ goto done;
+ } else
+ sc->TargetName = NULL;
+ if (cmh->len[2])
+ sc->InitiatorName = pdu_getbuf(pdu, NULL, 3);
+ else
+ sc->InitiatorName = NULL;
+
+log_debug("session %s to %s", sc->SessionName, log_sockaddr(&sc->connection.TargetAddr));
+ s = session_find(initiator, sc->SessionName);
+ if (s == NULL) {
+ s = session_new(initiator, sc->SessionType);
+ if (s == NULL) {
+ control_compose(ch, CTRL_FAILURE, NULL, 0);
+ goto done;
+ }
+ }
+
+ session_config(s, sc);
+
+ if (s->state == SESS_FREE) {
+ log_debug("new connection to %s",
+ log_sockaddr(&s->config.connection.TargetAddr));
+ conn_new(s, &s->config.connection);
+ }
+
+ control_compose(ch, CTRL_SUCCESS, NULL, 0);
+ break;
+ default:
+ log_warnx("unknown control message type %d", cmh->type);
+ control_compose(ch, CTRL_FAILURE, NULL, 0);
+ break;
+ }
+
+done:
+ pdu_free(pdu);
+}
diff --git a/usr.sbin/iscsid/iscsid.h b/usr.sbin/iscsid/iscsid.h
new file mode 100644
index 00000000000..1e3ebe42176
--- /dev/null
+++ b/usr.sbin/iscsid/iscsid.h
@@ -0,0 +1,297 @@
+/* $OpenBSD: iscsid.h,v 1.1 2010/09/24 09:43:19 claudio Exp $ */
+
+/*
+ * Copyright (c) 2009 Claudio Jeker <claudio@openbsd.org>
+ *
+ * Permission to use, copy, modify, and distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+
+#define ISCSID_DEVICE "/dev/vscsi0"
+#define ISCSID_CONTROL "/var/run/iscsid.sock"
+#define ISCSID_CONFIG "/etc/iscsi.conf"
+#define ISCSID_USER "_iscsid"
+
+#define ISCSID_BASE_NAME "iqn.1995-11.org.openbsd.iscsid"
+
+#define PDU_READ_SIZE (256 * 1024)
+#define CONTROL_READ_SIZE 8192
+#define PDU_MAXIOV 5
+#define PDU_WRIOV (PDU_MAXIOV * 8)
+
+#define PDU_HEADER 0
+#define PDU_AHS 1
+#define PDU_HDIGEST 2
+#define PDU_DATA 3
+#define PDU_DDIGEST 4
+
+#define PDU_LEN(x) ((((x) + 3) / 4) * 4)
+
+/*
+ * Common control message header.
+ * A message can consist of up to 3 parts with specified length.
+ */
+struct ctrlmsghdr {
+ u_int16_t type;
+ u_int16_t len[3];
+};
+
+/* Control message types */
+#define CTRL_SUCCESS 1
+#define CTRL_FAILURE 2
+#define CTRL_INITIATOR_CONFIG 3
+#define CTRL_SESSION_CONFIG 4
+
+
+TAILQ_HEAD(session_head, session);
+TAILQ_HEAD(connection_head, connection);
+TAILQ_HEAD(pduq, pdu);
+TAILQ_HEAD(taskq, task);
+
+/* as in tcp_seq.h */
+#define SEQ_LT(a,b) ((int)((a)-(b)) < 0)
+#define SEQ_LEQ(a,b) ((int)((a)-(b)) <= 0)
+#define SEQ_GT(a,b) ((int)((a)-(b)) > 0)
+#define SEQ_GEQ(a,b) ((int)((a)-(b)) >= 0)
+
+#define SESS_FREE 0x0001
+#define SESS_LOGGED_IN 0x0002
+#define SESS_FAILED 0x0003
+
+#define CONN_FREE 0x0001
+#define CONN_XPT_WAIT 0x0002
+#define CONN_XPT_UP 0x0004
+#define CONN_IN_LOGIN 0x0008
+#define CONN_LOGGED_IN 0x0010
+#define CONN_IN_LOGOUT 0x0020
+#define CONN_LOGOUT_REQ 0x0040
+#define CONN_CLEANUP_WAIT 0x0080
+#define CONN_IN_CLEANUP 0x0100
+#define CONN_ANYSTATE 0xffff
+
+enum c_event {
+ CONN_EV_FAIL,
+ CONN_EV_CONNECT,
+ CONN_EV_CONNECTED,
+ CONN_EV_LOGGED_IN,
+ CONN_EV_DISCOVERY,
+ CONN_EV_CLOSE
+};
+
+struct pdu {
+ TAILQ_ENTRY(pdu) entry;
+ struct iovec iov[PDU_MAXIOV];
+ size_t resid;
+};
+
+struct pdu_readbuf {
+ char *buf;
+ size_t size;
+ size_t rpos;
+ size_t wpos;
+ struct pdu *wip;
+};
+
+struct connection_config {
+ /* values inherited from session_config */
+ struct sockaddr_storage TargetAddr; /* IP:port of target */
+ struct sockaddr_storage LocalAddr; /* IP:port of target */
+};
+
+struct initiator_config {
+ u_int32_t isid_base; /* only 24 bits */
+ u_int16_t isid_qual;
+ u_int16_t pad;
+};
+
+struct session_config {
+ /* unique session ID */
+ char SessionName[32];
+ /*
+ * I = initialize only, L = leading only
+ * S = session wide, C = connection only
+ */
+ struct connection_config connection;
+
+ char *TargetName; /* String: IS */
+
+ char *InitiatorName; /* String: IS */
+
+ u_int16_t MaxConnections;
+ /* 1, 1-65535 (min()): LS */
+ u_int8_t HeaderDigest;
+ /* None , (None|CRC32): IC */
+ u_int8_t DataDigest;
+ /* None , (None|CRC32): IC */
+ u_int8_t SessionType;
+ /* Normal, (Discovery|Normal): LS */
+ u_int8_t disabled;
+};
+
+#define SESSION_TYPE_NORMAL 0
+#define SESSION_TYPE_DISCOVERY 1
+
+struct session_params {
+ u_int32_t MaxBurstLength;
+ /* 262144, 512-to-(2**24-1) (min()): LS */
+ u_int32_t FirstBurstLength;
+ /* 65536, 512-to-(2**24-1) (min()): LS */
+ u_int16_t DefaultTime2Wait;
+ /* 2, 0-to-3600 (max()): LS */
+ u_int16_t DefaultTime2Retain;
+ /* 20, 0-to-3600 (min()): LS */
+ u_int16_t MaxOutstandingR2T;
+ /* 1, 1-to-65535 (min()): LS */
+ u_int16_t TargetPortalGroupTag;
+ /* 1- 65535: IS */
+ u_int8_t InitialR2T;
+ /* yes, bool (||): LS */
+ u_int8_t ImmediateData;
+ /* yes, bool (&&): LS */
+ u_int8_t DataPDUInOrder;
+ /* yes, bool (||): LS */
+ u_int8_t DataSequenceInOrder;
+ /* yes, bool (||): LS */
+ u_int8_t ErrorRecoveryLevel;
+ /* 0, 0 - 2 (min()): LS */
+};
+
+struct connection_params {
+ u_int32_t MaxRecvDataSegmentLength;
+ /* 8192, 512-to-(2**24-1): C */
+ /* inherited from session_config */
+ u_int8_t HeaderDigest;
+ u_int8_t DataDigest;
+};
+
+struct initiator {
+ struct session_head sessions;
+ struct initiator_config config;
+ u_int target;
+};
+
+struct session {
+ TAILQ_ENTRY(session) entry;
+ struct connection_head connections;
+ struct taskq tasks;
+ struct session_config config;
+ struct session_params mine;
+ struct session_params his;
+ struct session_params active;
+ struct initiator *initiator;
+ u_int32_t cmdseqnum;
+ u_int32_t itt;
+ u_int32_t isid_base; /* only 24 bits */
+ u_int16_t isid_qual; /* inherited from initiator */
+ u_int16_t tsih; /* target session id handle */
+ u_int target;
+ int state;
+};
+
+struct connection {
+ struct event ev;
+ struct event wev;
+ TAILQ_ENTRY(connection) entry;
+ struct connection_params mine;
+ struct connection_params his;
+ struct connection_params active;
+ struct connection_config config;
+ struct pdu_readbuf prbuf;
+ struct pduq pdu_w;
+ struct taskq tasks;
+ struct session *session;
+ u_int32_t expstatsn;
+ int state;
+ int fd;
+ u_int16_t cid; /* conection id */
+};
+
+struct task {
+ TAILQ_ENTRY(task) entry;
+ struct pduq sendq;
+ struct pduq recvq;
+ void *callarg;
+ void (*callback)(struct connection *, void *, struct pdu *);
+ u_int32_t cmdseqnum;
+ u_int32_t itt;
+};
+
+struct kvp {
+ char *key;
+ char *value;
+ long flags;
+};
+#define KVP_KEY_ALLOCED 0x01
+#define KVP_VALUE_ALLOCED 0x02
+
+void iscsid_ctrl_dispatch(void *, struct pdu *);
+
+struct initiator *initiator_init(void);
+void initiator_cleanup(struct initiator *);
+struct session *initiator_t2s(u_int);
+
+int control_init(char *);
+void control_cleanup(char *);
+int control_listen(void);
+int control_queue(void *, struct pdu *);
+int control_compose(void *, u_int16_t, void *, size_t);
+
+struct session *session_find(struct initiator *, char *);
+struct session *session_new(struct initiator *, u_int8_t);
+void session_close(struct session *);
+void session_config(struct session *, struct session_config *);
+void session_task_issue(struct session *, struct task *);
+void session_schedule(struct session *);
+void session_task_login(struct connection *);
+void initiator_login(struct connection *);
+void initiator_discovery(struct session *);
+
+void conn_new(struct session *, struct connection_config *);
+void conn_free(struct connection *);
+int conn_task_issue(struct connection *, struct task *);
+void conn_task_schedule(struct connection *);
+void conn_pdu_write(struct connection *, struct pdu *);
+void conn_close(struct connection *);
+void conn_fail(struct connection *);
+void conn_loggedin(struct connection *);
+
+void *pdu_gethdr(struct pdu *);
+int text_to_pdu(struct kvp *, struct pdu *);
+struct kvp *pdu_to_text(char *, size_t);
+void pdu_free_queue(struct pduq *);
+ssize_t pdu_read(struct connection *);
+ssize_t pdu_write(struct connection *);
+int pdu_pending(struct connection *);
+void pdu_parse(struct connection *);
+int pdu_readbuf_set(struct pdu_readbuf *, size_t);
+void pdu_readbuf_free(struct pdu_readbuf *);
+
+struct pdu *pdu_new(void);
+void *pdu_alloc(size_t);
+void *pdu_dup(void *, size_t);
+int pdu_addbuf(struct pdu *, void *, size_t, unsigned int);
+void *pdu_getbuf(struct pdu *, size_t *, unsigned int);
+void pdu_free(struct pdu *);
+int socket_setblockmode(int, int);
+const char *log_sockaddr(void *);
+
+void task_init(struct task *, struct session *, int, void *,
+ void (*)(struct connection *, void *, struct pdu *));
+void task_cleanup(struct task *, struct connection *c);
+void task_pdu_add(struct task *, struct pdu *);
+void task_pdu_cb(struct connection *, struct pdu *);
+
+void vscsi_open(char *);
+void vscsi_dispatch(int, short, void *);
+void vscsi_data(unsigned long, int, void *, size_t);
+void vscsi_status(int, int, void *, size_t);
+void vscsi_event(unsigned long, u_int, u_int);
diff --git a/usr.sbin/iscsid/log.c b/usr.sbin/iscsid/log.c
new file mode 100644
index 00000000000..088cb207775
--- /dev/null
+++ b/usr.sbin/iscsid/log.c
@@ -0,0 +1,225 @@
+/* $OpenBSD: log.c,v 1.1 2010/09/24 09:43:19 claudio Exp $ */
+
+/*
+ * Copyright (c) 2009 Claudio Jeker <claudio@openbsd.org>
+ * Copyright (c) 2003, 2004 Henning Brauer <henning@openbsd.org>
+ *
+ * Permission to use, copy, modify, and distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+#include <sys/types.h>
+#include <sys/queue.h>
+#include <sys/socket.h>
+#include <sys/uio.h>
+
+#include <scsi/iscsi.h>
+
+#include <errno.h>
+#include <event.h>
+#include <netdb.h>
+#include <stdarg.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <syslog.h>
+#include <unistd.h>
+
+#include "iscsid.h"
+#include "log.h"
+
+int debug;
+
+void logit(int, const char *, ...);
+
+void
+log_init(int n_debug)
+{
+ extern char *__progname;
+
+ debug = n_debug;
+
+ if (!debug)
+ openlog(__progname, LOG_PID | LOG_NDELAY, LOG_DAEMON);
+
+ tzset();
+}
+
+void
+logit(int pri, const char *fmt, ...)
+{
+ va_list ap;
+
+ va_start(ap, fmt);
+ vlog(pri, fmt, ap);
+ va_end(ap);
+}
+
+void
+vlog(int pri, const char *fmt, va_list ap)
+{
+ char *nfmt;
+
+ if (debug) {
+ /* best effort in out of mem situations */
+ if (asprintf(&nfmt, "%s\n", fmt) == -1) {
+ vfprintf(stderr, fmt, ap);
+ fprintf(stderr, "\n");
+ } else {
+ vfprintf(stderr, nfmt, ap);
+ free(nfmt);
+ }
+ fflush(stderr);
+ } else
+ vsyslog(pri, fmt, ap);
+}
+
+void
+log_warn(const char *emsg, ...)
+{
+ char *nfmt;
+ va_list ap;
+
+ /* best effort to even work in out of memory situations */
+ if (emsg == NULL)
+ logit(LOG_CRIT, "%s", strerror(errno));
+ else {
+ va_start(ap, emsg);
+
+ if (asprintf(&nfmt, "%s: %s", emsg, strerror(errno)) == -1) {
+ /* we tried it... */
+ vlog(LOG_CRIT, emsg, ap);
+ logit(LOG_CRIT, "%s", strerror(errno));
+ } else {
+ vlog(LOG_CRIT, nfmt, ap);
+ free(nfmt);
+ }
+ va_end(ap);
+ }
+}
+
+void
+log_warnx(const char *emsg, ...)
+{
+ va_list ap;
+
+ va_start(ap, emsg);
+ vlog(LOG_CRIT, emsg, ap);
+ va_end(ap);
+}
+
+void
+log_info(const char *emsg, ...)
+{
+ va_list ap;
+
+ va_start(ap, emsg);
+ vlog(LOG_INFO, emsg, ap);
+ va_end(ap);
+}
+
+void
+log_debug(const char *emsg, ...)
+{
+ va_list ap;
+
+ if (debug) {
+ va_start(ap, emsg);
+ vlog(LOG_DEBUG, emsg, ap);
+ va_end(ap);
+ }
+}
+
+void
+fatal(const char *emsg)
+{
+ if (emsg == NULL)
+ logit(LOG_CRIT, "fatal: %s", strerror(errno));
+ else
+ if (errno)
+ logit(LOG_CRIT, "fatal: %s: %s", emsg, strerror(errno));
+ else
+ logit(LOG_CRIT, "fatal: %s", emsg);
+
+ exit(1);
+}
+
+void
+fatalx(const char *emsg)
+{
+ errno = 0;
+ fatal(emsg);
+}
+
+void
+log_hexdump(void *buf, size_t len)
+{
+ u_char b[16];
+ size_t i, j, l;
+
+ if (!debug)
+ return;
+
+ for (i = 0; i < len; i += l) {
+ fprintf(stderr, "%4zi:", i);
+ l = sizeof(b) < len - i ? sizeof(b) : len - i;
+ bcopy((char *)buf + i, b, l);
+
+ for (j = 0; j < sizeof(b); j++) {
+ if (j % 2 == 0)
+ fprintf(stderr, " ");
+ if (j % 8 == 0)
+ fprintf(stderr, " ");
+ if (j < l)
+ fprintf(stderr, "%02x", (int)b[j]);
+ else
+ fprintf(stderr, " ");
+ }
+ fprintf(stderr, " |");
+ for (j = 0; j < l; j++) {
+ if (b[j] >= 0x20 && b[j] <= 0x7e)
+ fprintf(stderr, "%c", b[j]);
+ else
+ fprintf(stderr, ".");
+ }
+ fprintf(stderr, "|\n");
+ }
+}
+
+void
+log_pdu(struct pdu *p, int all)
+{
+ struct iscsi_pdu *pdu;
+ void *b;
+ size_t s;
+
+ if (!debug)
+ return;
+
+ if (!(pdu = pdu_getbuf(p, NULL, PDU_HEADER))) {
+ log_debug("empty pdu");
+ return;
+ }
+
+ fprintf(stderr, "PDU: op %x%s flags %02x%02x%02x ahs %d len %d\n",
+ ISCSI_PDU_OPCODE(pdu->opcode), ISCSI_PDU_I(pdu) ? " I" : "",
+ pdu->flags, pdu->_reserved1[0], pdu->_reserved1[1],
+ pdu->ahslen, pdu->datalen[0] << 16 | pdu->datalen[1] << 8 |
+ pdu->datalen[2]);
+ fprintf(stderr, " lun %02x%02x%02x%02x%02x%02x%02x%02x itt %d "
+ "cmdsn %d expstatsn %d\n", pdu->lun[0], pdu->lun[1], pdu->lun[2],
+ pdu->lun[3], pdu->lun[4], pdu->lun[5], pdu->lun[6], pdu->lun[7],
+ ntohl(pdu->itt), ntohl(pdu->cmdsn), ntohl(pdu->expstatsn));
+ log_hexdump(pdu, sizeof(*pdu));
+
+ if (all && (b = pdu_getbuf(p, &s, PDU_DATA)))
+ log_hexdump(b, s);
+}
diff --git a/usr.sbin/iscsid/log.h b/usr.sbin/iscsid/log.h
new file mode 100644
index 00000000000..a8658ea2223
--- /dev/null
+++ b/usr.sbin/iscsid/log.h
@@ -0,0 +1,35 @@
+/* $OpenBSD: log.h,v 1.1 2010/09/24 09:43:19 claudio Exp $ */
+
+/*
+ * Copyright (c) 2003, 2004 Henning Brauer <henning@openbsd.org>
+ *
+ * Permission to use, copy, modify, and distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+
+#ifndef _LOG_H_
+#define _LOG_H_
+
+#include <stdarg.h>
+
+void log_init(int);
+void vlog(int, const char *, va_list);
+void log_warn(const char *, ...);
+void log_warnx(const char *, ...);
+void log_info(const char *, ...);
+void log_debug(const char *, ...);
+void fatal(const char *) __dead;
+void fatalx(const char *) __dead;
+void log_hexdump(void *, size_t);
+void log_pdu(struct pdu *, int);
+
+#endif /* _LOG_H_ */
diff --git a/usr.sbin/iscsid/pdu.c b/usr.sbin/iscsid/pdu.c
new file mode 100644
index 00000000000..86072b1128d
--- /dev/null
+++ b/usr.sbin/iscsid/pdu.c
@@ -0,0 +1,371 @@
+/* $OpenBSD: pdu.c,v 1.1 2010/09/24 09:43:19 claudio Exp $ */
+
+/*
+ * Copyright (c) 2009 Claudio Jeker <claudio@openbsd.org>
+ *
+ * Permission to use, copy, modify, and distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+#include <sys/types.h>
+#include <sys/queue.h>
+#include <sys/socket.h>
+#include <sys/uio.h>
+
+#include <scsi/iscsi.h>
+
+#include <errno.h>
+#include <event.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <strings.h>
+#include <unistd.h>
+
+#include "iscsid.h"
+#include "log.h"
+
+size_t pdu_readbuf_read(struct pdu_readbuf *, void *, size_t);
+size_t pdu_readbuf_len(struct pdu_readbuf *);
+
+#define PDU_MIN(_x, _y) ((_x) < (_y) ? (_x) : (_y))
+
+void *
+pdu_gethdr(struct pdu *p)
+{
+ void *hdr;
+
+ if (!(hdr = calloc(1, sizeof(struct iscsi_pdu))))
+ return NULL;
+ if (pdu_addbuf(p, hdr, sizeof(struct iscsi_pdu), PDU_HEADER)) {
+ free(hdr);
+ return NULL;
+ }
+ return hdr;
+}
+
+int
+text_to_pdu(struct kvp *k, struct pdu *p)
+{
+ char *buf, *s;
+ size_t len = 0, rem;
+ int n, nk;
+
+ nk = 0;
+ while(k[nk].key) {
+ len += 2 + strlen(k[nk].key) + strlen(k[nk].value);
+ nk++;
+ }
+
+ if (!(buf = pdu_alloc(len)))
+ return -1;
+ s = buf;
+ rem = len;
+ nk = 0;
+ while(k[nk].key) {
+ n = snprintf(s, rem, "%s=%s", k[nk].key, k[nk].value);
+ if (n == -1 || (size_t)n >= rem)
+ fatalx("text_to_pdu");
+ rem -= n + 1;
+ s += n + 1;
+ nk++;
+ }
+
+ if (pdu_addbuf(p, buf, len, PDU_DATA))
+ return -1;
+ return len;
+}
+
+struct kvp *
+pdu_to_text(char *buf, size_t len)
+{
+ struct kvp *k;
+ size_t n;
+ char *eq;
+ unsigned int nkvp = 0, i;
+
+ if (buf[len - 1]) {
+ log_debug("pdu_to_text: badly terminated text data");
+ return NULL;
+ }
+ for(n = 0; n < len; n++)
+ if (buf[n] == '\0')
+ nkvp++;
+
+ if (!(k = calloc(nkvp + 1, sizeof(*k))))
+ return NULL;
+
+ for (i = 0; i < nkvp; i++) {
+ eq = strchr(buf, '=');
+ if (!eq) {
+ log_debug("pdu_to_text: badly encoded text data");
+ free(k);
+ return NULL;
+ }
+ *eq++ = '\0';
+ k[i].key = buf;
+ k[i].value = eq;
+ buf = eq + strlen(eq) + 1;
+ }
+ return k;
+}
+
+/*
+ * Internal functions to send/recv pdus.
+ */
+
+void
+pdu_free_queue(struct pduq *channel)
+{
+ struct pdu *p;
+
+ while ((p = TAILQ_FIRST(channel))) {
+ TAILQ_REMOVE(channel, p, entry);
+ pdu_free(p);
+ }
+}
+
+ssize_t
+pdu_read(struct connection *c)
+{
+ struct iovec iov[2];
+ unsigned int niov = 1;
+ ssize_t n;
+
+ bzero(&iov, sizeof(iov));
+ iov[0].iov_base = c->prbuf.buf + c->prbuf.wpos;
+ if (c->prbuf.wpos < c->prbuf.rpos)
+ iov[0].iov_len = c->prbuf.rpos - c->prbuf.wpos;
+ else {
+ iov[0].iov_len = c->prbuf.size - c->prbuf.wpos;
+ if (c->prbuf.rpos > 0) {
+ niov++;
+ iov[1].iov_base = c->prbuf.buf;
+ iov[1].iov_len = c->prbuf.rpos - 1;
+ }
+ }
+
+ if ((n = readv(c->fd, iov, niov)) == -1) {
+ if (errno == EAGAIN || errno == ENOBUFS ||
+ errno == EINTR) /* try later */
+ return 0;
+ else {
+ log_warn("pdu_read");
+ return -1;
+ }
+ }
+ if (n == 0)
+ /* XXX what should we do on close with remaining data? */
+ return 0;
+
+ c->prbuf.wpos += n;
+ if (c->prbuf.wpos >= c->prbuf.size)
+ c->prbuf.wpos -= c->prbuf.size;
+
+ return (n);
+}
+
+ssize_t
+pdu_write(struct connection *c)
+{
+ struct iovec iov[PDU_WRIOV];
+ struct pdu *b, *nb;
+ unsigned int niov = 0, j;
+ size_t off, resid, size;
+ ssize_t n;
+
+ TAILQ_FOREACH(b, &c->pdu_w, entry) {
+ if (niov >= PDU_WRIOV)
+ break;
+ off = b->resid;
+ for (j = 0; j < PDU_MAXIOV && niov < PDU_WRIOV; j++) {
+ if (!b->iov[j].iov_len)
+ continue;
+ if (off >= b->iov[j].iov_len) {
+ off -= b->iov[j].iov_len;
+ continue;
+ }
+ iov[niov].iov_base = (char *)b->iov[j].iov_base + off;
+ iov[niov++].iov_len = b->iov[j].iov_len - off;
+ off = 0;
+ }
+ }
+
+ if ((n = writev(c->fd, iov, niov)) == -1) {
+ if (errno == EAGAIN || errno == ENOBUFS ||
+ errno == EINTR) /* try later */
+ return 0;
+ else {
+ log_warn("pdu_write");
+ return -1;
+ }
+ }
+ if (n == 0)
+ return 0;
+
+ size = n;
+ for (b = TAILQ_FIRST(&c->pdu_w); b != NULL && size > 0; b = nb) {
+ nb = TAILQ_NEXT(b, entry);
+ resid = b->resid;
+ for (j = 0; j < PDU_MAXIOV; j++) {
+ if (resid >= b->iov[j].iov_len)
+ resid -= b->iov[j].iov_len;
+ else if (size >= b->iov[j].iov_len - resid) {
+ size -= b->iov[j].iov_len - resid;
+ b->resid += b->iov[j].iov_len - resid;
+ resid = 0;
+ } else {
+ b->resid += size;
+ size = 0;
+ break;
+ }
+ }
+ if (j == PDU_MAXIOV) {
+ /* all written */
+ TAILQ_REMOVE(&c->pdu_w, b, entry);
+ pdu_free(b);
+ }
+ }
+ return n;
+}
+
+int
+pdu_pending(struct connection *c)
+{
+ if (TAILQ_EMPTY(&c->pdu_w))
+ return 0;
+ else
+ return 1;
+}
+
+void
+pdu_parse(struct connection *c)
+{
+ struct pdu *p;
+ struct iscsi_pdu *ipdu;
+ char *ahb, *db;
+ size_t ahslen, dlen, off;
+ ssize_t n;
+ unsigned int j;
+
+/* XXX XXX I DON'T LIKE YOU. CAN I REWRITE YOU? */
+
+ do {
+ if (!(p = c->prbuf.wip)) {
+ /* get and parse base header */
+ if (pdu_readbuf_len(&c->prbuf) < sizeof(*ipdu))
+ return;
+ if (!(p = pdu_new()))
+ goto fail;
+ if (!(ipdu = pdu_gethdr(p)))
+ goto fail;
+
+ c->prbuf.wip = p;
+ /*
+ * XXX maybe a pdu_readbuf_peek() would allow a better
+ * error handling.
+ */
+ pdu_readbuf_read(&c->prbuf, ipdu, sizeof(*ipdu));
+
+ ahslen = ipdu->ahslen * sizeof(u_int32_t);
+ if (ahslen != 0) {
+ if (!(ahb = pdu_alloc(ahslen)) ||
+ pdu_addbuf(p, ahb, ahslen, PDU_AHS))
+ goto fail;
+ }
+
+ dlen = ipdu->datalen[0] << 16 | ipdu->datalen[1] << 8 |
+ ipdu->datalen[2];
+ if (dlen != 0) {
+ if (!(db = pdu_alloc(dlen)) ||
+ pdu_addbuf(p, db, dlen, PDU_DATA))
+ goto fail;
+ }
+
+ p->resid = sizeof(*ipdu);
+ } else {
+ off = p->resid;
+ for (j = 0; j < PDU_MAXIOV; j++) {
+ if (off >= p->iov[j].iov_len)
+ off -= p->iov[j].iov_len;
+ else {
+ n = pdu_readbuf_read(&c->prbuf,
+ (char *)p->iov[j].iov_base + off,
+ p->iov[j].iov_len - off);
+ p->resid += n;
+ if (n == 0 || off + n !=
+ p->iov[j].iov_len)
+ return;
+ }
+ }
+ task_pdu_cb(c, p);
+ c->prbuf.wip = NULL;
+ }
+ } while (1);
+fail:
+ fatalx("pdu_parse hit a space oddity");
+}
+
+size_t
+pdu_readbuf_read(struct pdu_readbuf *rb, void *ptr, size_t len)
+{
+ size_t l;
+
+ if (rb->rpos == rb->wpos) {
+ return (0);
+ } else if (rb->rpos < rb->wpos) {
+ l = PDU_MIN(rb->wpos - rb->rpos, len);
+ bcopy(rb->buf + rb->rpos, ptr, l);
+ rb->rpos += l;
+ return l;
+ } else {
+ l = PDU_MIN(rb->size - rb->rpos, len);
+ bcopy(rb->buf + rb->rpos, ptr, l);
+ rb->rpos += l;
+ if (rb->rpos == rb->size)
+ rb->rpos = 0;
+ if (l < len)
+ return l + pdu_readbuf_read(rb, (char *)ptr + l,
+ len - l);
+ return l;
+ }
+}
+
+size_t
+pdu_readbuf_len(struct pdu_readbuf *rb)
+{
+ if (rb->rpos <= rb->wpos)
+ return rb->wpos - rb->rpos;
+ else
+ return rb->size - (rb->rpos - rb->wpos);
+}
+
+int
+pdu_readbuf_set(struct pdu_readbuf *rb, size_t bsize)
+{
+ char *nb;
+
+ if (bsize < rb->size)
+ /* can't shrink */
+ return 0;
+ if ((nb = realloc(rb->buf, bsize)) == NULL) {
+ free(rb->buf);
+ return -1;
+ }
+ rb->buf = nb;
+ rb->size = bsize;
+ return 0;
+}
+
+void
+pdu_readbuf_free(struct pdu_readbuf *rb)
+{
+ free(rb->buf);
+}
diff --git a/usr.sbin/iscsid/task.c b/usr.sbin/iscsid/task.c
new file mode 100644
index 00000000000..70a4c85534c
--- /dev/null
+++ b/usr.sbin/iscsid/task.c
@@ -0,0 +1,124 @@
+/* $OpenBSD: task.c,v 1.1 2010/09/24 09:43:19 claudio Exp $ */
+
+/*
+ * Copyright (c) 2009 Claudio Jeker <claudio@openbsd.org>
+ *
+ * Permission to use, copy, modify, and distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+#include <sys/types.h>
+#include <sys/queue.h>
+#include <sys/socket.h>
+#include <sys/uio.h>
+
+#include <scsi/iscsi.h>
+
+#include <errno.h>
+#include <event.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <strings.h>
+#include <unistd.h>
+
+#include "iscsid.h"
+#include "log.h"
+
+/*
+ * Task handling, PDU are attached to tasks and task are scheduled accross
+ * all connections of a session.
+ */
+
+void
+task_init(struct task *t, struct session *s, int immediate, void *carg,
+ void (*c)(struct connection *, void *, struct pdu *))
+{
+ TAILQ_INIT(&t->sendq);
+ TAILQ_INIT(&t->recvq);
+ t->callback = c;
+ t->callarg = carg;
+ t->itt = s->itt++; /* XXX we could do better here */
+ t->cmdseqnum = s->cmdseqnum;
+ if (!immediate)
+ s->cmdseqnum++;
+}
+
+void
+task_cleanup(struct task *t, struct connection *c)
+{
+/* XXX THIS FEELS WRONG FOR NOW */
+ pdu_free_queue(&t->sendq);
+ pdu_free_queue(&t->recvq);
+ /* XXX need some state to know if queued or not */
+ TAILQ_REMOVE(&c->tasks, t, entry);
+}
+
+void
+task_pdu_add(struct task *t, struct pdu *p)
+{
+ struct iscsi_pdu *ipdu;
+
+ /* fixup the pdu by setting the itt and seqnum if needed */
+ ipdu = pdu_getbuf(p, NULL, PDU_HEADER);
+ ipdu->itt = ntohl(t->itt);
+ switch (ISCSI_PDU_OPCODE(ipdu->opcode)) {
+ case ISCSI_OP_I_NOP:
+ case ISCSI_OP_SCSI_REQUEST:
+ case ISCSI_OP_TASK_REQUEST:
+ case ISCSI_OP_LOGIN_REQUEST:
+ case ISCSI_OP_TEXT_REQUEST:
+ case ISCSI_OP_LOGOUT_REQUEST:
+ ipdu->cmdsn = ntohl(t->cmdseqnum);
+ break;
+ }
+
+ TAILQ_INSERT_TAIL(&t->sendq, p, entry);
+}
+
+void
+task_pdu_cb(struct connection *c, struct pdu *p)
+{
+ struct task *t;
+ struct iscsi_pdu *ipdu;
+ u_int32_t itt;
+
+ ipdu = pdu_getbuf(p, NULL, PDU_HEADER);
+ switch (ISCSI_PDU_OPCODE(ipdu->opcode)) {
+ case ISCSI_OP_T_NOP:
+ case ISCSI_OP_LOGIN_RESPONSE:
+ case ISCSI_OP_TEXT_RESPONSE:
+ case ISCSI_OP_LOGOUT_RESPONSE:
+ case ISCSI_OP_SCSI_RESPONSE:
+ case ISCSI_OP_R2T:
+ case ISCSI_OP_DATA_IN:
+ itt = ntohl(ipdu->itt);
+ c->expstatsn = ntohl(ipdu->cmdsn) + 1;
+
+ /* XXX for now search the task on the connection queue
+ later on this should be moved to a per session RB tree but
+ now I do the quick ugly thing. */
+ TAILQ_FOREACH(t, &c->tasks, entry) {
+ if (itt == t->itt)
+ break;
+ }
+ if (t)
+ t->callback(c, t->callarg, p);
+ else {
+ log_debug("no task for PDU found");
+ pdu_free(p);
+ }
+ break;
+ default:
+log_pdu(p, 1);
+ log_warnx("not handled yet. fix me");
+ pdu_free(p);
+ }
+}
diff --git a/usr.sbin/iscsid/util.c b/usr.sbin/iscsid/util.c
new file mode 100644
index 00000000000..8e1242efbd4
--- /dev/null
+++ b/usr.sbin/iscsid/util.c
@@ -0,0 +1,143 @@
+/* $OpenBSD: util.c,v 1.1 2010/09/24 09:43:19 claudio Exp $ */
+
+/*
+ * Copyright (c) 2009 Claudio Jeker <claudio@openbsd.org>
+ *
+ * Permission to use, copy, modify, and distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+#include <sys/types.h>
+#include <sys/queue.h>
+#include <sys/socket.h>
+#include <sys/uio.h>
+
+#include <scsi/iscsi.h>
+
+#include <errno.h>
+#include <event.h>
+#include <fcntl.h>
+#include <netdb.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <strings.h>
+#include <unistd.h>
+
+#include "iscsid.h"
+#include "log.h"
+
+struct pdu *
+pdu_new(void)
+{
+ struct pdu *p;
+
+ if (!(p = calloc(1, sizeof(*p))))
+ return NULL;
+ return p;
+}
+
+void *
+pdu_alloc(size_t len)
+{
+ return malloc(PDU_LEN(len));
+}
+
+void *
+pdu_dup(void *data, size_t len)
+{
+ void *p;
+
+ if ((p = malloc(PDU_LEN(len))))
+ bcopy(data, p, len);
+ return p;
+}
+
+int
+pdu_addbuf(struct pdu *p, void *buf, size_t len, unsigned int elm)
+{
+ if (len & 0x3) {
+ bzero((char *)buf + len, 4 - (len & 0x3));
+ len += 4 - (len & 0x3);
+ }
+
+ if (elm < PDU_MAXIOV)
+ if (!p->iov[elm].iov_base) {
+ p->iov[elm].iov_base = buf;
+ p->iov[elm].iov_len = len;
+ return 0;
+ }
+
+ /* no space left */
+ return -1;
+}
+
+void *
+pdu_getbuf(struct pdu *p, size_t *len, unsigned int elm)
+{
+ if (len)
+ *len = 0;
+ if (elm < PDU_MAXIOV)
+ if (p->iov[elm].iov_base) {
+ if (len)
+ *len = p->iov[elm].iov_len;
+ return p->iov[elm].iov_base;
+ }
+
+ return NULL;
+}
+
+void
+pdu_free(struct pdu *p)
+{
+ unsigned int j;
+
+ for (j = 0; j < PDU_MAXIOV; j++)
+ free(p->iov[j].iov_base);
+ free(p);
+}
+
+int
+socket_setblockmode(int fd, int nonblocking)
+{
+ int flags;
+
+ if ((flags = fcntl(fd, F_GETFL, 0)) == -1)
+ return -1;
+
+ if (nonblocking)
+ flags |= O_NONBLOCK;
+ else
+ flags &= ~O_NONBLOCK;
+
+ if ((flags = fcntl(fd, F_SETFL, flags)) == -1)
+ return -1;
+ return 0;
+}
+
+const char *
+log_sockaddr(void *arg)
+{
+ struct sockaddr *sa = arg;
+ char port[6];
+ char host[NI_MAXHOST];
+ static char buf[NI_MAXHOST + 8];
+
+ if (getnameinfo(sa, sa->sa_len, host, sizeof(host), port, sizeof(port),
+ NI_NUMERICHOST))
+ return "unknown";
+ if (port[0] == '0')
+ strlcpy(buf, host, sizeof(buf));
+ else if (sa->sa_family == AF_INET)
+ snprintf(buf, sizeof(buf), "%s:%s", host, port);
+ else
+ snprintf(buf, sizeof(buf), "[%s]:%s", host, port);
+ return buf;
+}
diff --git a/usr.sbin/iscsid/vscsi.c b/usr.sbin/iscsid/vscsi.c
new file mode 100644
index 00000000000..1517ff397bb
--- /dev/null
+++ b/usr.sbin/iscsid/vscsi.c
@@ -0,0 +1,286 @@
+/* $OpenBSD: vscsi.c,v 1.1 2010/09/24 09:43:19 claudio Exp $ */
+
+/*
+ * Copyright (c) 2009 Claudio Jeker <claudio@openbsd.org>
+ *
+ * Permission to use, copy, modify, and distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+
+#include <sys/types.h>
+#include <sys/ioctl.h>
+#include <sys/queue.h>
+#include <sys/socket.h>
+#include <sys/uio.h>
+
+#include <scsi/iscsi.h>
+#include <scsi/scsi_all.h>
+#include <dev/vscsivar.h>
+
+#include <event.h>
+#include <fcntl.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include "iscsid.h"
+#include "log.h"
+
+struct vscsi {
+ struct event ev;
+ int fd;
+} v;
+
+struct scsi_task {
+ struct task task;
+ int tag;
+ u_int target;
+ u_int lun;
+ size_t datalen;
+};
+
+void vscsi_callback(struct connection *, void *, struct pdu *);
+void vscsi_dataout(struct session *, struct scsi_task *, u_int32_t, size_t);
+
+void
+vscsi_open(char *dev)
+{
+ if ((v.fd = open(dev, O_RDWR)) == -1)
+ fatal("vscsi_open");
+
+ event_set(&v.ev, v.fd, EV_READ|EV_PERSIST, vscsi_dispatch, NULL);
+ event_add(&v.ev, NULL);
+}
+
+void
+vscsi_dispatch(int fd, short event, void *arg)
+{
+ struct vscsi_ioc_i2t i2t;
+ struct iscsi_pdu_scsi_request *sreq;
+ struct session *s;
+ struct scsi_task *t;
+ struct pdu *p;
+#if 0
+ char *buf;
+ u_int32_t t32;
+#endif
+
+ if (!(event & EV_READ)) {
+ log_debug("spurious read call");
+ return;
+ }
+
+ if (ioctl(v.fd, VSCSI_I2T, &i2t) == -1)
+ fatal("vscsi_dispatch");
+
+ s = initiator_t2s(i2t.target);
+ if (s == NULL)
+ fatalx("vscsi_dispatch: unknown target");
+
+ if (!(t = calloc(1, sizeof(*t))))
+ fatal("vscsi_dispatch");
+
+ t->tag = i2t.tag;
+ t->target = i2t.target;
+ t->lun = i2t.lun;
+
+ if (!(p = pdu_new()))
+ fatal("vscsi_dispatch");
+ if (!(sreq = pdu_gethdr(p)))
+ fatal("vscsi_dispatch");
+
+ sreq->opcode = ISCSI_OP_SCSI_REQUEST;
+ /* XXX use untagged commands, dlg sais so */
+ sreq->flags = ISCSI_SCSI_F_F | ISCSI_SCSI_ATTR_UNTAGGED;
+ if (i2t.direction == VSCSI_DIR_WRITE)
+ sreq->flags |= ISCSI_SCSI_F_W;
+ if (i2t.direction == VSCSI_DIR_READ)
+ sreq->flags |= ISCSI_SCSI_F_R;
+ sreq->bytes = htonl(i2t.datalen);
+
+ /* XXX LUN HANDLING !!!! */
+
+ bcopy(&i2t.cmd, sreq->cdb, i2t.cmdlen);
+
+#if 0
+ if (i2t.direction == VSCSI_DIR_WRITE) {
+ if (!(buf = pdu_alloc(i2t.datalen)))
+ fatal("vscsi_dispatch");
+ t32 = htonl(i2t.datalen);
+ bcopy(&t32, &sreq->ahslen, sizeof(t32));
+ vscsi_data(VSCSI_DATA_WRITE, i2t.tag, buf, i2t.datalen);
+ pdu_addbuf(p, buf, i2t.datalen, PDU_DATA);
+ }
+#endif
+
+ task_init(&t->task, s, 0, t, vscsi_callback);
+ task_pdu_add(&t->task, p);
+ session_task_issue(s, &t->task);
+}
+
+void
+vscsi_data(unsigned long req, int tag, void *buf, size_t len)
+{
+ struct vscsi_ioc_data data;
+
+ data.tag = tag;
+ data.data = buf;
+ data.datalen = len;
+
+ if (ioctl(v.fd, req, &data) == -1)
+ fatal("vscsi_data");
+}
+
+void
+vscsi_status(int tag, int status, void *buf, size_t len)
+{
+ struct vscsi_ioc_t2i t2i;
+
+ bzero(&t2i, sizeof(t2i));
+ t2i.tag = tag;
+ t2i.status = status;
+ if (buf) {
+ if (len > sizeof(t2i.sense))
+ fatal("vscsi_status: I'm sorry, Dave. "
+ "I'm afraid I can't do that.");
+ bcopy(buf, &t2i.sense, len);
+ }
+
+ if (ioctl(v.fd, VSCSI_T2I, &t2i) == -1)
+ fatal("vscsi_status");
+}
+
+void
+vscsi_event(unsigned long req, u_int target, u_int lun)
+{
+ struct vscsi_ioc_devevent devev;
+
+ devev.target = target;
+ devev.lun = lun;
+
+ if (ioctl(v.fd, req, &devev) == -1)
+ fatal("vscsi_event");
+}
+
+void
+vscsi_callback(struct connection *c, void *arg, struct pdu *p)
+{
+ struct scsi_task *t = arg;
+ struct iscsi_pdu_scsi_response *sresp;
+ struct iscsi_pdu_rt2 *r2t;
+ int status = VSCSI_STAT_DONE;
+ u_char *buf = NULL;
+ size_t size = 0, n;
+ int tag;
+
+ sresp = pdu_getbuf(p, NULL, PDU_HEADER);
+ switch (ISCSI_PDU_OPCODE(sresp->opcode)) {
+ case ISCSI_OP_SCSI_RESPONSE:
+ task_cleanup(&t->task, c);
+ tag = t->tag;
+ free(t);
+
+ if (!(sresp->flags & 0x80) || (sresp->flags & 0x06) == 0x06 ||
+ (sresp->flags & 0x18) == 0x18) {
+ log_debug("vscsi_callback: bad scsi response");
+ conn_fail(c);
+ break;
+ }
+ /* XXX handle the various serial numbers */
+ if (sresp->response) {
+ status = VSCSI_STAT_ERR;
+ goto send_status;
+ }
+ switch (sresp->status) {
+ case ISCSI_SCSI_STAT_GOOD:
+ break;
+ case ISCSI_SCSI_STAT_CHCK_COND:
+ status = VSCSI_STAT_SENSE;
+ /* stupid encoding of sense data in the data segment */
+ buf = pdu_getbuf(p, &n, PDU_DATA);
+ if (buf) {
+ size = buf[0] << 8 | buf[1];
+ buf += 2;
+ }
+ break;
+ default:
+ status = VSCSI_STAT_ERR;
+ break;
+ }
+send_status:
+ vscsi_status(tag, status, buf, size);
+ break;
+ case ISCSI_OP_DATA_IN:
+ buf = pdu_getbuf(p, &n, PDU_DATA);
+ size = sresp->datalen[0] << 16 | sresp->datalen[1] << 8 |
+ sresp->datalen[2];
+ if (size > n)
+ fatal("This does not work as it should");
+ vscsi_data(VSCSI_DATA_READ, t->tag, buf, size);
+ if (sresp->flags & 1) {
+ log_debug("and a vscsi_status");
+ task_cleanup(&t->task, c);
+ vscsi_status(t->tag, status, NULL, 0);
+ free(t);
+ }
+ break;
+ case ISCSI_OP_R2T:
+ task_cleanup(&t->task, c);
+ r2t = (struct iscsi_pdu_rt2 *)sresp;
+ if (ntohl(r2t->buffer_offs))
+ fatalx("vscsi: r2t bummer failure");
+ size = ntohl(r2t->desired_datalen);
+
+ vscsi_dataout(c->session, t, r2t->ttt, size);
+ break;
+ default:
+ log_debug("scsi task: tag %d, target %d lun %d", t->tag,
+ t->target, t->lun);
+ log_pdu(p, 1);
+ }
+ pdu_free(p);
+}
+
+void
+vscsi_dataout(struct session *s, struct scsi_task *t, u_int32_t ttt, size_t len)
+{
+ struct pdu *p;
+ struct iscsi_pdu_data_out *dout;
+ u_char *buf = NULL;
+ size_t size, off;
+ u_int32_t t32, dsn = 0;
+
+ for (off = 0; off < len; off += size) {
+ size = len - off > 8 * 1024 ? 8 * 1024 : len - off;
+
+ if (!(p = pdu_new()))
+ fatal("vscsi_r2t");
+ if (!(dout = pdu_gethdr(p)))
+ fatal("vscsi_r2t");
+
+ dout->opcode = ISCSI_OP_DATA_OUT;
+ if (off + size == len)
+ dout->flags = 0x80; /* XXX magic value: F flag*/
+ dout->ttt = ttt;
+ dout->datasn = htonl(dsn++);
+ t32 = htonl(size);
+ bcopy(&t32, &dout->ahslen, sizeof(t32));
+
+ dout->buffer_offs = htonl(off);
+ if (!(buf = pdu_alloc(size)))
+ fatal("vscsi_r2t");
+
+ vscsi_data(VSCSI_DATA_WRITE, t->tag, buf, size);
+ pdu_addbuf(p, buf, size, PDU_DATA);
+ task_pdu_add(&t->task, p);
+ }
+ session_task_issue(s, &t->task);
+}