File iscsitarget-svn-174.diff of Package iscsitarget

Index: kernel/wthread.c
===================================================================
--- kernel/wthread.c	(revision 138)
+++ kernel/wthread.c	(working copy)
@@ -120,7 +120,9 @@
 	int err;
 
 	assert(wt->w_task);
-	if ((err = kthread_stop(wt->w_task)) < 0)
+	err = kthread_stop(wt->w_task);
+
+	if (err < 0 && err != -EINTR)
 		return err;
 
 	list_del(&wt->w_list);
Index: kernel/block-io.c
===================================================================
--- kernel/block-io.c	(revision 138)
+++ kernel/block-io.c	(working copy)
@@ -9,8 +9,9 @@
  * This code is licenced under the GPL.
  */
 
+#include <linux/types.h>
+#include <linux/blkdev.h>
 #include <linux/parser.h>
-#include <linux/blkdev.h>
 #include <linux/buffer_head.h>
 
 #include "iscsi.h"
@@ -28,15 +29,10 @@
 	struct completion tio_complete;
 };
 
-static int
-blockio_bio_endio(struct bio *bio, unsigned int bytes_done, int error)
+static void blockio_bio_endio(struct bio *bio, int error)
 {
 	struct tio_work *tio_work = bio->bi_private;
 
-	/* Ignore partials */
-	if (bio->bi_size)
-		return 1;
-
 	error = test_bit(BIO_UPTODATE, &bio->bi_flags) ? error : -EIO;
 
 	if (error)
@@ -47,8 +43,6 @@
 		complete(&tio_work->tio_complete);
 
 	bio_put(bio);
-
-	return 0;
 }
 
 /*
@@ -254,8 +248,9 @@
 };
 
 static int
-parse_blockio_params (struct iet_volume *volume, char *params)
+parse_blockio_params(struct iet_volume *volume, char *params)
 {
+	struct blockio_data *info = volume->private;
 	int err = 0;
 	char *p, *q;
 
@@ -290,6 +285,13 @@
 				goto out;
 			break;
 		case Opt_path:
+			if (info->path) {
+				iprintk("Target %s, LUN %u: "
+					"duplicate \"Path\" param\n",
+					volume->target->name, volume->lun);
+				err = -EINVAL;
+				goto out;
+			}
 			if (!(q = match_strdup(&args[0]))) {
 				err = -ENOMEM;
 				goto out;
@@ -302,12 +304,17 @@
 		case Opt_ignore:
 			break;
 		default:
-			eprintk("Bad option %s for Lun %u on Target %s \n",
-				p, volume->lun, volume->target->name);
+			iprintk("Target %s, LUN %u: unknown param %s\n",
+				volume->target->name, volume->lun, p);
 			return -EINVAL;
 		}
 	}
 
+	if (!info->path) {
+		iprintk("Target %s, LUN %u: missing \"Path\" param\n",
+			volume->target->name, volume->lun);
+		err = -EINVAL;
+	}
   out:
 	return err;
 }
@@ -319,12 +326,13 @@
 
 	if (bio_data->bdev)
 		close_bdev_excl(bio_data->bdev);
+	kfree(bio_data->path);
 
 	kfree(volume->private);
 }
 
 static int
-blockio_attach (struct iet_volume *volume, char *args)
+blockio_attach(struct iet_volume *volume, char *args)
 {
 	struct blockio_data *bio_data;
 	int err = 0;
@@ -350,6 +358,10 @@
 	/* Assign a vendor id, generate scsi id if none exists */
 	gen_scsiid(volume, bio_data->bdev->bd_inode);
 
+	/* Offer neither write nor read caching */
+	ClearLURCache(volume);
+	ClearLUWCache(volume);
+
 	volume->blk_shift = SECTOR_SIZE_BITS;
 	volume->blk_cnt = bio_data->bdev->bd_inode->i_size >> volume->blk_shift;
 
Index: kernel/target_disk.c
===================================================================
--- kernel/target_disk.c	(revision 138)
+++ kernel/target_disk.c	(working copy)
@@ -22,15 +22,18 @@
 	return sizeof(disconnect_pg);
 }
 
-static int insert_caching_pg(u8 *ptr, int async)
+static int insert_caching_pg(u8 *ptr, int wcache, int rcache)
 {
 	unsigned char caching_pg[] = {0x08, 0x12, 0x10, 0x00, 0xff, 0xff, 0x00, 0x00,
 				      0xff, 0xff, 0xff, 0xff, 0x80, 0x14, 0x00, 0x00,
 				      0x00, 0x00, 0x00, 0x00};
 
 	memcpy(ptr, caching_pg, sizeof(caching_pg));
-	if (async)
+	if (wcache)
 		ptr[2] |= 0x04;	/* set WCE bit if we're caching writes */
+	if (!rcache)
+		ptr[2] |= 0x01; /* Read Cache Disable */
+
 	return sizeof(caching_pg);
 }
 
@@ -66,13 +69,15 @@
 	unsigned char geo_m_pg[] = {0x04, 0x16, 0x00, 0x00, 0x00, 0x40, 0x00, 0x00,
 				    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
 				    0x00, 0x00, 0x00, 0x00, 0x3a, 0x98, 0x00, 0x00};
-	u32 ncyl, *p;
+	u32 ncyl;
+	u32 n;
 
 	/* assume 0xff heads, 15krpm. */
 	memcpy(ptr, geo_m_pg, sizeof(geo_m_pg));
 	ncyl = sec >> 14; /* 256 * 64 */
-	p = (u32 *)(ptr + 1);
-	*p = *p | cpu_to_be32(ncyl);
+	memcpy(&n, ptr+1, sizeof(u32));
+	n = n | cpu_to_be32(ncyl);
+	memcpy(ptr+1, &n, sizeof(u32));
 	return sizeof(geo_m_pg);
 }
 
@@ -84,6 +89,10 @@
 	int len = 4, err = 0;
 	u8 pcode;
 
+	/* changeable parameter mode pages are unsupported */
+	if ((scb[2] & 0xc0) >> 6 == 0x1)
+		return -1;
+
 	pcode = req->scb[2] & 0x3f;
 
 	assert(!tio);
@@ -118,7 +127,8 @@
 		len += insert_geo_m_pg(data + len, cmnd->lun->blk_cnt);
 		break;
 	case 0x8:
-		len += insert_caching_pg(data + len, LUAsync(cmnd->lun));
+		len += insert_caching_pg(data + len, LUWCache(cmnd->lun),
+					 LURCache(cmnd->lun));
 		break;
 	case 0xa:
 		len += insert_ctrl_m_pg(data + len);
@@ -130,7 +140,8 @@
 		len += insert_disconnect_pg(data + len);
 		len += insert_format_m_pg(data + len);
 		len += insert_geo_m_pg(data + len, cmnd->lun->blk_cnt);
-		len += insert_caching_pg(data + len, LUAsync(cmnd->lun));
+		len += insert_caching_pg(data + len, LUWCache(cmnd->lun),
+					 LURCache(cmnd->lun));
 		len += insert_ctrl_m_pg(data + len);
 		len += insert_iec_m_pg(data + len);
 		break;
@@ -153,7 +164,11 @@
 	u8 *scb = req->scb;
 	int err = -1;
 
-	if (((req->scb[1] & 0x3) == 0x3) || (!(req->scb[1] & 0x3) && req->scb[2]))
+	/*
+	 * - CmdDt and EVPD both set or EVPD and Page Code set: illegal
+	 * - CmdDt set: not supported
+	 */
+	if ((scb[1] & 0x3) > 0x1 || (!(scb[1] & 0x3) && scb[2]))
 		return err;
 
 	assert(!tio);
@@ -182,13 +197,6 @@
 		data[63] = 0x00;
 		tio_set(tio, 64, 0);
 		err = 0;
-	} else if (scb[1] & 0x2) {
-		/* CmdDt bit is set */
-		/* We do not support it now. */
-		data[1] = 0x1;
-		data[5] = 0;
-		tio_set(tio, 6, 0);
-		err = 0;
 	} else if (scb[1] & 0x1) {
 		/* EVPD bit set */
 		if (scb[2] == 0x0) {
@@ -249,7 +257,8 @@
 	struct iet_volume *lun;
 	int rest, idx = 0;
 
-	size = be32_to_cpu(*(u32 *)&req->scb[6]);
+	size = (u32)req->scb[6] << 24 | (u32)req->scb[7] << 16 |
+		(u32)req->scb[8] << 8 | (u32)req->scb[9];
 	if (size < 16)
 		return -1;
 
@@ -363,7 +372,7 @@
 
 	list_del_init(&cmnd->list);
 	err = tio_write(cmnd->lun, tio);
-	if (!err && !LUAsync(cmnd->lun))
+	if (!err && !LUWCache(cmnd->lun))
 		err = tio_sync(cmnd->lun, tio);
 
 	return err;
Index: kernel/config.c
===================================================================
--- kernel/config.c	(revision 138)
+++ kernel/config.c	(working copy)
@@ -40,7 +40,7 @@
 	int i;
 	struct proc_dir_entry *ent;
 
-	if (!(proc_iet_dir = proc_mkdir("net/iet", 0)))
+	if (!(proc_iet_dir = proc_mkdir("iet", init_net.proc_net)))
 		goto err;
 
 	proc_iet_dir->owner = THIS_MODULE;
Index: kernel/volume.c
===================================================================
--- kernel/volume.c	(revision 138)
+++ kernel/volume.c	(working copy)
@@ -4,12 +4,13 @@
  * This code is licenced under the GPL.
  */
 
+#include <linux/types.h>
+#include <linux/parser.h>
+
 #include "iscsi.h"
 #include "iscsi_dbg.h"
 #include "iotype.h"
 
-#include <linux/parser.h>
-
 struct iet_volume *volume_lookup(struct iscsi_target *target, u32 lun)
 {
 	struct iet_volume *volume;
@@ -63,7 +64,7 @@
 			if (argp && !strcmp(argp, "ro"))
 				SetLUReadonly(volume);
 			else if (argp && !strcmp(argp, "wb"))
-				SetLUAsync(volume);
+				SetLUWCache(volume);
 			kfree(argp);
 			break;
 		default:
@@ -132,6 +133,8 @@
 	list_add_tail(&volume->list, &target->volumes);
 	atomic_inc(&target->nr_volumes);
 
+	kfree(args);
+
 	return 0;
 free_args:
 	kfree(args);
@@ -230,10 +233,11 @@
 			   volume->lun, volume->l_state, volume->iotype->name);
 		if (LUReadonly(volume))
 			seq_printf(seq, " iomode:ro");
-		else if (LUAsync(volume))
+		else if (LUWCache(volume))
 			seq_printf(seq, " iomode:wb");
 		else
 			seq_printf(seq, " iomode:wt");
+
 		if (volume->iotype->show)
 			volume->iotype->show(volume, seq);
 		else
Index: kernel/iscsi.c
===================================================================
--- kernel/iscsi.c	(revision 138)
+++ kernel/iscsi.c	(working copy)
@@ -16,7 +16,7 @@
 unsigned long debug_enable_flags;
 
 static struct kmem_cache *iscsi_cmnd_cache;
-static char dummy_data[1024];
+static u8 dummy_data[PAGE_SIZE];
 
 static int ctr_major;
 static char ctr_name[] = "ietctl";
@@ -726,13 +726,18 @@
 	case READ_10:
 	case WRITE_10:
 	case WRITE_VERIFY:
-		*off = be32_to_cpu(*(u32 *)&cmd[2]);
+		*off = (u32)cmd[2] << 24 | (u32)cmd[3] << 16 |
+			(u32)cmd[4] << 8 | (u32)cmd[5];
 		*len = (cmd[7] << 8) + cmd[8];
 		break;
 	case READ_16:
 	case WRITE_16:
-		*off = be64_to_cpu(*(u64 *)&cmd[2]);
-		*len = be32_to_cpu(*(u32 *)&cmd[10]);
+		*off = (u64)cmd[2] << 56 | (u64)cmd[3] << 48 |
+			(u64)cmd[4] << 40 | (u64)cmd[5] << 32 |
+			(u64)cmd[6] << 24 | (u64)cmd[7] << 16 |
+			(u64)cmd[8] << 8 | (u64)cmd[9];
+		*len = (u32)cmd[10] << 24 | (u32)cmd[11] << 16 |
+			(u32)cmd[12] << 8 | (u32)cmd[13];
 		break;
 	default:
 		BUG();
Index: kernel/null-io.c
===================================================================
--- kernel/null-io.c	(revision 138)
+++ kernel/null-io.c	(working copy)
@@ -7,9 +7,10 @@
  * data. It is only for performance measurement purpose.
  */
 
+#include <linux/types.h>
 #include <linux/blkdev.h>
+#include <linux/parser.h>
 #include <linux/writeback.h>
-#include <linux/parser.h>
 
 #include "iscsi.h"
 #include "iscsi_dbg.h"
Index: kernel/iscsi.h
===================================================================
--- kernel/iscsi.h	(revision 138)
+++ kernel/iscsi.h	(working copy)
@@ -11,8 +11,8 @@
 #include <linux/seq_file.h>
 #include <linux/mm.h>
 #include <linux/crypto.h>
+#include <linux/scatterlist.h>
 #include <net/sock.h>
-#include <asm/scatterlist.h>
 
 #include "iscsi_hdr.h"
 #include "iet_u.h"
@@ -66,6 +66,7 @@
 
 	void (*old_state_change)(struct sock *);
 	void (*old_data_ready)(struct sock *, int);
+	void (*old_write_space)(struct sock *);
 };
 
 struct worker_thread_info;
@@ -153,15 +154,21 @@
 
 enum lu_flags {
 	LU_READONLY,
-	LU_ASYNC,
+	LU_WCACHE,
+	LU_RCACHE,
 };
 
 #define LUReadonly(lu) test_bit(LU_READONLY, &(lu)->flags)
 #define SetLUReadonly(lu) set_bit(LU_READONLY, &(lu)->flags)
 
-#define LUAsync(lu) test_bit(LU_ASYNC, &(lu)->flags)
-#define SetLUAsync(lu) set_bit(LU_ASYNC, &(lu)->flags)
+#define LUWCache(lu) test_bit(LU_WCACHE, &(lu)->flags)
+#define SetLUWCache(lu) set_bit(LU_WCACHE, &(lu)->flags)
+#define ClearLUWCache(lu) clear_bit(LU_WCACHE, &(lu)->flags)
 
+#define LURCache(lu) test_bit(LU_RCACHE, &(lu)->flags)
+#define SetLURCache(lu) set_bit(LU_RCACHE, &(lu)->flags)
+#define ClearLURCache(lu) clear_bit(LU_RCACHE, &(lu)->flags)
+
 #define IET_HASH_ORDER		8
 #define	cmnd_hashfn(itt)	hash_long((itt), IET_HASH_ORDER)
 
@@ -191,6 +198,7 @@
 enum connection_state_bit {
 	CONN_ACTIVE,
 	CONN_CLOSING,
+	CONN_WSPACE_WAIT,
 };
 
 #define ISCSI_CONN_IOV_MAX	(((256 << 10) >> PAGE_SHIFT) + 1)
@@ -300,6 +308,7 @@
 extern int nthread_init(struct iscsi_target *);
 extern int nthread_start(struct iscsi_target *);
 extern int nthread_stop(struct iscsi_target *);
+extern void __nthread_wakeup(struct network_thread_info *);
 extern void nthread_wakeup(struct iscsi_target *);
 
 /* wthread.c */
Index: kernel/digest.c
===================================================================
--- kernel/digest.c	(revision 138)
+++ kernel/digest.c	(working copy)
@@ -4,8 +4,7 @@
  * This code is licensed under the GPL.
  */
 
-#include <asm/types.h>
-#include <asm/scatterlist.h>
+#include <linux/types.h>
 
 #include "iscsi.h"
 #include "digest.h"
@@ -161,22 +160,17 @@
 	}
 }
 
-/* Copied from linux-iscsi initiator and slightly adjusted */
-#define SETSG(sg, p, l) do {					\
-	(sg).page = virt_to_page((p));				\
-	(sg).offset = ((unsigned long)(p) & ~PAGE_CACHE_MASK);	\
-	(sg).length = (l);					\
-} while (0)
-
 static void digest_header(struct hash_desc *hash, struct iscsi_pdu *pdu,
 			  u8 *crc)
 {
 	struct scatterlist sg[2];
 	unsigned int nbytes = sizeof(struct iscsi_hdr);
 
-	SETSG(sg[0], &pdu->bhs, nbytes);
+	sg_init_table(sg, pdu->ahssize ? 2 : 1);
+
+	sg_set_buf(&sg[0], &pdu->bhs, nbytes);
 	if (pdu->ahssize) {
-		SETSG(sg[1], pdu->ahs, pdu->ahssize);
+		sg_set_buf(&sg[1], pdu->ahs, pdu->ahssize);
 		nbytes += pdu->ahssize;
 	}
 
@@ -220,6 +214,7 @@
 
 	assert(count <= ISCSI_CONN_IOV_MAX);
 
+	sg_init_table(sg, ARRAY_SIZE(cmnd->conn->hash_sg));
 	crypto_hash_init(hash);
 
 	for (i = 0; size; i++) {
@@ -228,13 +223,13 @@
 		else
 			length = size;
 
-		sg[i].page = tio->pvec[idx + i];
-		sg[i].offset = offset;
-		sg[i].length = length;
+		sg_set_page(&sg[i], tio->pvec[idx + i], length, offset);
 		size -= length;
 		offset = 0;
 	}
 
+	sg_mark_end(&sg[i - 1]);
+
 	crypto_hash_update(hash, sg, nbytes);
 	crypto_hash_final(hash, crc);
 }
Index: kernel/event.c
===================================================================
--- kernel/event.c	(revision 138)
+++ kernel/event.c	(working copy)
@@ -28,7 +28,7 @@
 	return 0;
 }
 
-static int event_recv_skb(struct sk_buff *skb)
+static void event_recv_skb(struct sk_buff *skb)
 {
 	int err;
 	struct nlmsghdr	*nlh;
@@ -37,7 +37,7 @@
 	while (skb->len >= NLMSG_SPACE(0)) {
 		nlh = (struct nlmsghdr *)skb->data;
 		if (nlh->nlmsg_len < sizeof(*nlh) || skb->len < nlh->nlmsg_len)
-			return 0;
+			break;
 		rlen = NLMSG_ALIGN(nlh->nlmsg_len);
 		if (rlen > skb->len)
 			rlen = skb->len;
@@ -47,21 +47,8 @@
 			netlink_ack(skb, nlh, 0);
 		skb_pull(skb, rlen);
 	}
-	return 0;
 }
 
-static void event_recv(struct sock *sk, int length)
-{
-	struct sk_buff *skb;
-
-	while ((skb = skb_dequeue(&sk->sk_receive_queue))) {
-		if (event_recv_skb(skb) && skb->len)
-			skb_queue_head(&sk->sk_receive_queue, skb);
-		else
-			kfree_skb(skb);
-	}
-}
-
 static int notify(void *data, int len, int gfp_mask)
 {
 	struct sk_buff *skb;
@@ -95,8 +82,8 @@
 
 int event_init(void)
 {
-	nl = netlink_kernel_create(NETLINK_IET, 1, event_recv, NULL,
-				   THIS_MODULE);
+	nl = netlink_kernel_create(&init_net, NETLINK_IET, 1, event_recv_skb,
+				   NULL, THIS_MODULE);
 	if (!nl)
 		return -ENOMEM;
 	else
Index: kernel/file-io.c
===================================================================
--- kernel/file-io.c	(revision 138)
+++ kernel/file-io.c	(working copy)
@@ -4,9 +4,10 @@
  * This code is licenced under the GPL.
  */
 
+#include <linux/types.h>
 #include <linux/blkdev.h>
+#include <linux/parser.h>
 #include <linux/writeback.h>
-#include <linux/parser.h>
 
 #include "iscsi.h"
 #include "iscsi_dbg.h"
@@ -184,6 +185,7 @@
 
 static int parse_fileio_params(struct iet_volume *volume, char *params)
 {
+	struct fileio_data *info = volume->private;
 	int err = 0;
 	char *p, *q;
 
@@ -215,6 +217,13 @@
 				goto out;
 			break;
 		case Opt_path:
+			if (info->path) {
+				iprintk("Target %s, LUN %u: "
+					"duplicate \"Path\" param\n",
+					volume->target->name, volume->lun);
+				err = -EINVAL;
+				goto out;
+			}
 			if (!(q = match_strdup(&args[0]))) {
 				err = -ENOMEM;
 				goto out;
@@ -227,11 +236,17 @@
 		case Opt_ignore:
 			break;
 		default:
-			eprintk("Unknown %s\n", p);
+			iprintk("Target %s, LUN %u: unknown param %s\n",
+				volume->target->name, volume->lun, p);
 			return -EINVAL;
 		}
 	}
 
+	if (!info->path) {
+		iprintk("Target %s, LUN %u: missing \"Path\" param\n",
+			volume->target->name, volume->lun);
+		err = -EINVAL;
+	}
 out:
 	return err;
 }
@@ -284,6 +299,8 @@
 	lu->blk_shift = SECTOR_SIZE_BITS;
 	lu->blk_cnt = inode->i_size >> lu->blk_shift;
 
+	/* we're using the page cache */
+	SetLURCache(lu);
 out:
 	if (err < 0)
 		fileio_detach(lu);
Index: kernel/nthread.c
===================================================================
--- kernel/nthread.c	(revision 138)
+++ kernel/nthread.c	(working copy)
@@ -18,13 +18,18 @@
 	D_DATA_READY,
 };
 
+void __nthread_wakeup(struct network_thread_info *info)
+{
+	set_bit(D_DATA_READY, &info->flags);
+	wake_up_process(info->task);
+}
+
 void nthread_wakeup(struct iscsi_target *target)
 {
 	struct network_thread_info *info = &target->nthread_info;
 
 	spin_lock_bh(&info->nthread_lock);
-	set_bit(D_DATA_READY, &info->flags);
-	wake_up_process(info->task);
+	__nthread_wakeup(info);
 	spin_unlock_bh(&info->nthread_lock);
 }
 
@@ -283,6 +288,23 @@
 	return 0;
 }
 
+/*
+ * @locking: grabs the target's nthread_lock to protect it from races with
+ * iet_write_space()
+ */
+static void set_conn_wspace_wait(struct iscsi_conn *conn)
+{
+	struct network_thread_info *info = &conn->session->target->nthread_info;
+	struct sock *sk = conn->sock->sk;
+
+	spin_lock_bh(&info->nthread_lock);
+
+	if (sk_stream_wspace(sk) < sk_stream_min_wspace(sk))
+		set_bit(CONN_WSPACE_WAIT, &conn->state);
+
+	spin_unlock_bh(&info->nthread_lock);
+}
+
 /* This is taken from the Ardis code. */
 static int write_data(struct iscsi_conn *conn)
 {
@@ -399,8 +421,11 @@
 	conn->write_offset = (idx << PAGE_CACHE_SHIFT) + offset;
  out_iov:
 	conn->write_size = size;
-	if ((saved_size == size) && res == -EAGAIN)
-		return res;
+	if (res == -EAGAIN) {
+		set_conn_wspace_wait(conn);
+		if (saved_size == size)
+			return res;
+	}
 
 	return saved_size - size;
 
@@ -557,6 +582,9 @@
 		goto out;
 	}
 
+	if (test_bit(CONN_WSPACE_WAIT, &conn->state))
+		goto out;
+
 	res = send(conn);
 
 	if (!list_empty(&conn->write_list) || conn->write_cmnd)
@@ -572,6 +600,7 @@
 static void close_conn(struct iscsi_conn *conn)
 {
 	struct iscsi_session *session = conn->session;
+	struct iscsi_target *target = session->target;
 	struct iscsi_cmnd *cmnd;
 
 	assert(conn);
@@ -579,8 +608,9 @@
 	conn->sock->ops->shutdown(conn->sock, 2);
 
 	write_lock_bh(&conn->sock->sk->sk_callback_lock);
-	conn->sock->sk->sk_state_change = session->target->nthread_info.old_state_change;
-	conn->sock->sk->sk_data_ready = session->target->nthread_info.old_data_ready;
+	conn->sock->sk->sk_state_change = target->nthread_info.old_state_change;
+	conn->sock->sk->sk_data_ready = target->nthread_info.old_data_ready;
+	conn->sock->sk->sk_write_space = target->nthread_info.old_write_space;
 	write_unlock_bh(&conn->sock->sk->sk_callback_lock);
 
 	fput(conn->file);
@@ -604,11 +634,11 @@
 		assert(0);
 	}
 
-	event_send(session->target->tid, session->sid, conn->cid, E_CONN_CLOSE, 0);
+	event_send(target->tid, session->sid, conn->cid, E_CONN_CLOSE, 0);
 	conn_free(conn);
 
 	if (list_empty(&session->conn_list))
-		session_del(session->target, session->sid);
+		session_del(target, session->sid);
 }
 
 static int istd(void *arg)
@@ -654,6 +684,7 @@
 
 	info->old_state_change = NULL;
 	info->old_data_ready = NULL;
+	info->old_write_space = NULL;
 
 	INIT_LIST_HEAD(&info->active_conns);
 
Index: kernel/conn.c
===================================================================
--- kernel/conn.c	(revision 138)
+++ kernel/conn.c	(working copy)
@@ -96,6 +96,28 @@
 	target->nthread_info.old_data_ready(sk, len);
 }
 
+/*
+ * @locking: grabs the target's nthread_lock to protect it from races with
+ * set_conn_wspace_wait()
+ */
+static void iet_write_space(struct sock *sk)
+{
+	struct iscsi_conn *conn = sk->sk_user_data;
+	struct network_thread_info *info = &conn->session->target->nthread_info;
+
+	spin_lock_bh(&info->nthread_lock);
+
+	if (sk_stream_wspace(sk) >= sk_stream_min_wspace(sk) &&
+	    test_bit(CONN_WSPACE_WAIT, &conn->state)) {
+		clear_bit(CONN_WSPACE_WAIT, &conn->state);
+		__nthread_wakeup(info);
+	}
+
+	spin_unlock_bh(&info->nthread_lock);
+
+	info->old_write_space(sk);
+}
+
 static void iet_socket_bind(struct iscsi_conn *conn)
 {
 	int opt = 1;
@@ -114,6 +136,9 @@
 
 	target->nthread_info.old_data_ready = conn->sock->sk->sk_data_ready;
 	conn->sock->sk->sk_data_ready = iet_data_ready;
+
+	target->nthread_info.old_write_space = conn->sock->sk->sk_write_space;
+	conn->sock->sk->sk_write_space = iet_write_space;
 	write_unlock_bh(&conn->sock->sk->sk_callback_lock);
 
 	oldfs = get_fs();
Index: include/iet_u.h
===================================================================
--- include/iet_u.h	(revision 138)
+++ include/iet_u.h	(working copy)
@@ -1,7 +1,7 @@
 #ifndef _IET_U_H
 #define _IET_U_H
 
-#define IET_VERSION_STRING	"0.4.15"
+#define IET_VERSION_STRING	"0.4.16"
 
 /* The maximum length of 223 bytes in the RFC. */
 #define ISCSI_NAME_LEN	256
Index: doc/manpages/ietadm.8
===================================================================
--- doc/manpages/ietadm.8	(revision 138)
+++ doc/manpages/ietadm.8	(working copy)
@@ -154,7 +154,7 @@
 create dynamically a new target, numbered 2. \s-1CAUTION\s0 : the target will disappear if you restart ietd, you'll have to edit /etc/ietd.conf to make it permanent!
 .SH "ERROR MESSAGES"
 .IX Header "ERROR MESSAGES"
-ietadm misses error messages. Look carefully the \s-1STDERR\s0 output : in case of error
+ietadm misses error messages. Look carefully at the \s-1STDERR\s0 output : in case of error
 it will send a 3 number error code, ending with \-1, for instance :
 .PP
 ietd_request 203 3 \-1
@@ -216,6 +216,17 @@
 If you don't specify a target (omit \-\-tid option),
 you add a new account for discovery sessions.
 .PP
+\&\fB\-\-op show \-\-tid=[id] \-\-user\fR
+.PP
+show a list of CHAP accounts.
+If \-\-tid is omitted or [id] is \*(L"0\*(R" (zero), discovery accounts are displayed.
+.PP
+\&\fB\-\-op show \-\-tid=[id] \-\-user \-\-params=[user]=[name]\fR
+.PP
+show CHAP account information for the account specified by [name].
+[user] can be [IncomingUser] or [OutgoingUser].
+If \-\-tid is omitted or [id] is \*(L"0\*(R" (zero), [name] is supposed to be a discovery account name.
+.PP
 \&\fB\-\-op delete \-\-tid=[id] \-\-user \-\-params=[user]=[name]\fR
 .PP
 delete specific account having [name] of specific
Index: doc/manpages/ietd.conf.5
===================================================================
--- doc/manpages/ietd.conf.5	(revision 138)
+++ doc/manpages/ietd.conf.5	(working copy)
@@ -202,7 +202,7 @@
 Optional. If set to "CRC32C" and the initiator is configured accordingly, the integrity of an iSCSI PDU's data segment will be protected by a CRC32C checksum. The default is "None". Note that data digests are not supported during discovery sessions.
 .TP 
 .B [MaxConnections <value>]
-Optional. Has to be set to "1" (in words: one), which is also the default.
+Optional. The number of connections within a session. Has to be set to "1" (in words: one), which is also the default since MC/S is not supported.
 .TP 
 .B [InitialR2T <Yes|No>]
 Optional. If set to "Yes" (default), the initiator has to wait for the target to solicit SCSI data before sending it. Setting it to "No" allows the initiator to send a burst of
Index: ChangeLog
===================================================================
--- ChangeLog	(revision 138)
+++ ChangeLog	(working copy)
@@ -1,3 +1,29 @@
+Summary of changes from v0.4.15 to v0.4.16
+=================================
+
+Arne Redlich
+  o fix overzealous assert() in digest_data()
+  o add checking on return value of ISCSI_PARAM_GET
+  o 2.6.22, 2.6.23 and 2.6.24 compile fixes
+  o add conn->rwsize check
+  o avoid potential NULL-ptr dereferences in rx and tx buffer 
+  o fix the shell syntax in init scripts
+
+Dave Jiang
+  o fix digest endieness on LE archs
+
+FUJITA Tomonori
+  o fix SPARC alignement issues (based on a patch from joel.bertrand@systella.fr)
+
+Ross S. W. Walker
+  o fix DISTDIR in Makefile for /etc install
+  o add support to nullio for volumes > 2TB
+  o remove init.d memory size adjustment
+  o add error code reporting to blockio_open_path
+  o blockio gen_scsiid bug fix
+  o add verbosity to kernel output and task management
+
+
 Summary of changes from v0.4.14 to v0.4.15
 =================================
 
Index: patches/compat-2.6.22-2.6.23.patch
===================================================================
--- patches/compat-2.6.22-2.6.23.patch	(revision 0)
+++ patches/compat-2.6.22-2.6.23.patch	(revision 174)
@@ -0,0 +1,152 @@
+Index: kernel/block-io.c
+===================================================================
+--- kernel/block-io.c	(working copy)
++++ kernel/block-io.c	(revision 145)
+@@ -28,10 +28,15 @@ struct tio_work {
+ 	struct completion tio_complete;
+ };
+ 
+-static void blockio_bio_endio(struct bio *bio, int error)
++static int
++blockio_bio_endio(struct bio *bio, unsigned int bytes_done, int error)
+ {
+ 	struct tio_work *tio_work = bio->bi_private;
+ 
++	/* Ignore partials */
++	if (bio->bi_size)
++		return 1;
++
+ 	error = test_bit(BIO_UPTODATE, &bio->bi_flags) ? error : -EIO;
+ 
+ 	if (error)
+@@ -42,6 +47,8 @@ static void blockio_bio_endio(struct bio
+ 		complete(&tio_work->tio_complete);
+ 
+ 	bio_put(bio);
++
++	return 0;
+ }
+ 
+ /*
+Index: kernel/config.c
+===================================================================
+--- kernel/config.c	(working copy)
++++ kernel/config.c	(revision 145)
+@@ -40,7 +40,7 @@ int iet_procfs_init(void)
+ 	int i;
+ 	struct proc_dir_entry *ent;
+ 
+-	if (!(proc_iet_dir = proc_mkdir("iet", init_net.proc_net)))
++	if (!(proc_iet_dir = proc_mkdir("net/iet", 0)))
+ 		goto err;
+ 
+ 	proc_iet_dir->owner = THIS_MODULE;
+Index: kernel/digest.c
+===================================================================
+--- kernel/digest.c	(working copy)
++++ kernel/digest.c	(revision 145)
+@@ -161,17 +161,22 @@ static inline void __dbg_simulate_data_d
+ 	}
+ }
+ 
++/* Copied from linux-iscsi initiator and slightly adjusted */
++#define SETSG(sg, p, l) do {					\
++	(sg).page = virt_to_page((p));				\
++	(sg).offset = ((unsigned long)(p) & ~PAGE_CACHE_MASK);	\
++	(sg).length = (l);					\
++} while (0)
++
+ static void digest_header(struct hash_desc *hash, struct iscsi_pdu *pdu,
+ 			  u8 *crc)
+ {
+ 	struct scatterlist sg[2];
+ 	unsigned int nbytes = sizeof(struct iscsi_hdr);
+ 
+-	sg_init_table(sg, pdu->ahssize ? 2 : 1);
+-
+-	sg_set_buf(&sg[0], &pdu->bhs, nbytes);
++	SETSG(sg[0], &pdu->bhs, nbytes);
+ 	if (pdu->ahssize) {
+-		sg_set_buf(&sg[1], pdu->ahs, pdu->ahssize);
++		SETSG(sg[1], pdu->ahs, pdu->ahssize);
+ 		nbytes += pdu->ahssize;
+ 	}
+ 
+@@ -215,7 +220,6 @@ static void digest_data(struct hash_desc
+ 
+ 	assert(count <= ISCSI_CONN_IOV_MAX);
+ 
+-	sg_init_table(sg, ARRAY_SIZE(cmnd->conn->hash_sg));
+ 	crypto_hash_init(hash);
+ 
+ 	for (i = 0; size; i++) {
+@@ -224,13 +228,13 @@ static void digest_data(struct hash_desc
+ 		else
+ 			length = size;
+ 
+-		sg_set_page(&sg[i], tio->pvec[idx + i], length, offset);
++		sg[i].page = tio->pvec[idx + i];
++		sg[i].offset = offset;
++		sg[i].length = length;
+ 		size -= length;
+ 		offset = 0;
+ 	}
+ 
+-	sg_mark_end(&sg[i - 1]);
+-
+ 	crypto_hash_update(hash, sg, nbytes);
+ 	crypto_hash_final(hash, crc);
+ }
+Index: kernel/event.c
+===================================================================
+--- kernel/event.c	(working copy)
++++ kernel/event.c	(revision 145)
+@@ -28,7 +28,7 @@ static int event_recv_msg(struct sk_buff
+ 	return 0;
+ }
+ 
+-static void event_recv_skb(struct sk_buff *skb)
++static int event_recv_skb(struct sk_buff *skb)
+ {
+ 	int err;
+ 	struct nlmsghdr	*nlh;
+@@ -37,7 +37,7 @@ static void event_recv_skb(struct sk_buf
+ 	while (skb->len >= NLMSG_SPACE(0)) {
+ 		nlh = (struct nlmsghdr *)skb->data;
+ 		if (nlh->nlmsg_len < sizeof(*nlh) || skb->len < nlh->nlmsg_len)
+-			break;
++			return 0;
+ 		rlen = NLMSG_ALIGN(nlh->nlmsg_len);
+ 		if (rlen > skb->len)
+ 			rlen = skb->len;
+@@ -47,6 +47,19 @@ static void event_recv_skb(struct sk_buf
+ 			netlink_ack(skb, nlh, 0);
+ 		skb_pull(skb, rlen);
+ 	}
++	return 0;
++}
++
++static void event_recv(struct sock *sk, int length)
++{
++	struct sk_buff *skb;
++
++	while ((skb = skb_dequeue(&sk->sk_receive_queue))) {
++		if (event_recv_skb(skb) && skb->len)
++			skb_queue_head(&sk->sk_receive_queue, skb);
++		else
++			kfree_skb(skb);
++	}
+ }
+ 
+ static int notify(void *data, int len, int gfp_mask)
+@@ -82,8 +95,8 @@ int event_send(u32 tid, u64 sid, u32 cid
+ 
+ int event_init(void)
+ {
+-	nl = netlink_kernel_create(&init_net, NETLINK_IET, 1, event_recv_skb,
+-				   NULL, THIS_MODULE);
++	nl = netlink_kernel_create(NETLINK_IET, 1, event_recv, NULL,
++				   THIS_MODULE);
+ 	if (!nl)
+ 		return -ENOMEM;
+ 	else
Index: patches/compat-2.6.14-2.6.18.patch
===================================================================
--- patches/compat-2.6.14-2.6.18.patch	(revision 138)
+++ patches/compat-2.6.14-2.6.18.patch	(working copy)
@@ -199,7 +199,7 @@
  
 -static struct kmem_cache *iscsi_cmnd_cache;
 +static kmem_cache_t *iscsi_cmnd_cache;
- static char dummy_data[1024];
+ static u8 dummy_data[PAGE_SIZE];
  
  static int ctr_major;
 Index: kernel/tio.c
Index: usr/misc.h
===================================================================
--- usr/misc.h	(revision 138)
+++ usr/misc.h	(working copy)
@@ -5,9 +5,9 @@
 #ifndef MISC_H
 #define MISC_H
 
-struct qelem {
-	struct qelem *q_forw;
-	struct qelem *q_back;
+struct __qelem {
+	struct __qelem *q_forw;
+	struct __qelem *q_back;
 };
 
 /* stolen list stuff from Linux kernel */
@@ -21,20 +21,20 @@
 
 #define LIST_HEAD_INIT(name) { &(name), &(name) }
 #define LIST_HEAD(name) \
-	struct qelem name = LIST_HEAD_INIT(name)
+	struct __qelem name = LIST_HEAD_INIT(name)
 
 #define INIT_LIST_HEAD(ptr) do { \
 	(ptr)->q_forw = (ptr); (ptr)->q_back = (ptr); \
 } while (0)
 
-static inline int list_empty(const struct qelem *head)
+static inline int list_empty(const struct __qelem *head)
 {
 	return head->q_forw == head;
 }
 
-static inline int list_length_is_one(const struct qelem *head)
+static inline int list_length_is_one(const struct __qelem *head)
 {
-        return head->q_forw == head->q_back;
+        return (!list_empty(head) && head->q_forw == head->q_back);
 }
 
 #define container_of(ptr, type, member) ({			\
Index: usr/ietadm.c
===================================================================
--- usr/ietadm.c	(revision 138)
+++ usr/ietadm.c	(working copy)
@@ -29,6 +29,8 @@
 #define	SET_LUNIT	(1 << 3)
 #define	SET_USER	(1 << 4)
 
+typedef int (user_handle_fn_t)(struct ietadm_req *req, char *user, char *pass);
+
 enum ietadm_op {
 	OP_NEW,
 	OP_DELETE,
@@ -72,6 +74,14 @@
                         show iSCSI parameters in effect for session [sid]. If\n\
                         [sid] is \"0\" (zero), the configured parameters\n\
                         will be displayed.\n\
+  --op show --tid=[id] --user\n\
+                        show list of Discovery (--tid omitted / id=0 (zero))\n\
+                        or target CHAP accounts.\n\
+  --op show --tid=[id] --user --params=[user]=[name]\n\
+                        show CHAP account information. [user] can be\n\
+                        \"IncomingUser\" or \"OutgoingUser\". If --tid is\n\
+                        omitted / id=0 (zero), [user] is treated as Discovery\n\
+                        user.\n\
   --op new --tid=[id] --lun=[lun] --params Path=[path]\n\
                         add a new logical unit with [lun] to specific\n\
                         target with [id]. The logical unit is offered\n\
@@ -142,7 +152,8 @@
 	return err;
 }
 
-static int ietd_response_recv(int fd, struct ietadm_req *req)
+static int ietd_response_recv(int fd, struct ietadm_req *req, void *rsp_data,
+			      size_t rsp_data_sz)
 {
 	int err, ret;
 	struct iovec iov[2];
@@ -161,6 +172,15 @@
 	} else
 		err = rsp.err;
 
+	if (!err && rsp_data_sz && rsp_data) {
+		ret = read(fd, rsp_data, rsp_data_sz);
+		if (ret != rsp_data_sz) {
+			err = (ret < 0) ? -errno : -EIO;
+			fprintf(stderr,  "%s %d %d %d\n", __FUNCTION__,
+				__LINE__, ret, err);
+		}
+	}
+
 	return err;
 }
 
@@ -183,7 +203,8 @@
 	return fd;
 }
 
-static int ietd_request(struct ietadm_req *req)
+static int ietd_request(struct ietadm_req *req, void *rsp_data,
+			size_t rsp_data_sz)
 {
 	int fd = -1, err = -EIO;
 
@@ -195,7 +216,7 @@
 	if ((err = ietd_request_send(fd, req)) < 0)
 		goto out;
 
-	err = ietd_response_recv(fd, req);
+	err = ietd_response_recv(fd, req, rsp_data, rsp_data_sz);
 
 out:
 	if (fd > 0)
@@ -318,7 +339,7 @@
 		break;
 	}
 
-	err = ietd_request(&req);
+	err = ietd_request(&req, NULL, 0);
 	if (!err && req.rcmnd == C_TRGT_SHOW)
 		show_iscsi_param(key_target, req.u.trgt.target_param);
 
@@ -357,7 +378,7 @@
 		break;
 	}
 
-	err = ietd_request(&req);
+	err = ietd_request(&req, NULL, 0);
 out:
 	return err;
 }
@@ -385,7 +406,7 @@
 		break;
 	case OP_SHOW:
 		req.rcmnd = C_SESS_SHOW;
-		err = ietd_request(&req);
+		err = ietd_request(&req, NULL, 0);
 		if (!err)
 			show_iscsi_param(key_session, req.u.trgt.session_param);
 		break;
@@ -395,31 +416,11 @@
 	return err;
 }
 
-static int user_handle(int op, u32 set, u32 tid, char *params)
+static int parse_user_params(char *params, u32 *auth_dir, char **user,
+			     char **pass)
 {
-	int err = -EINVAL;
-	char *p, *q, *user = NULL, *pass = NULL;
-	struct ietadm_req req;
+	char *p, *q;
 
-	if (set & ~(SET_TARGET | SET_USER))
-		goto out;
-
-	memset(&req, 0, sizeof(req));
-	req.tid = tid;
-
-	switch (op) {
-	case OP_NEW:
-		req.rcmnd = C_ACCT_NEW;
-		break;
-	case OP_DELETE:
-		req.rcmnd = C_ACCT_DEL;
-		break;
-	case OP_UPDATE:
-	case OP_SHOW:
-		fprintf(stderr, "Unsupported.\n");
-		goto out;
-	}
-
 	while ((p = strsep(&params, ",")) != NULL) {
 		if (!*p)
 			continue;
@@ -431,37 +432,187 @@
 			q++;
 
 		if (!strcasecmp(p, "IncomingUser")) {
-			if (user)
-				fprintf(stderr, "Already specified user %s\n", q);
-			user = q;
-			req.u.acnt.auth_dir = AUTH_DIR_INCOMING;
+			if (*user)
+				fprintf(stderr,
+					"Already specified IncomingUser %s\n",
+					q);
+			*user = q;
+			*auth_dir = AUTH_DIR_INCOMING;
 		} else if (!strcasecmp(p, "OutgoingUser")) {
-			if (user)
-				fprintf(stderr, "Already specified user %s\n", q);
-			user = q;
-			req.u.acnt.auth_dir = AUTH_DIR_OUTGOING;
+			if (*user)
+				fprintf(stderr,
+					"Already specified OutgoingUser %s\n",
+					q);
+			*user = q;
+			*auth_dir = AUTH_DIR_OUTGOING;
 		} else if (!strcasecmp(p, "Password")) {
-			if (pass)
-				fprintf(stderr, "Already specified pass %s\n", q);
-			pass = q;
+			if (*pass)
+				fprintf(stderr,
+					"Already specified Password %s\n", q);
+			*pass = q;
 		} else {
 			fprintf(stderr, "Unknown parameter %p\n", q);
-			goto out;
+			return -EINVAL;
 		}
 	}
+	return 0;
+}
 
-	if ((op == OP_NEW && ((user && !pass) || (!user && pass) || (!user && !pass))) ||
-	    (op == OP_DELETE && ((!user && pass) || (!user && !pass)))) {
-		fprintf(stderr,
-			"You need to specify a user and its password %s %s\n", pass, user);
-		goto out;
+static void show_account(int auth_dir, char *user, char *pass)
+{
+	char buf[(ISCSI_NAME_LEN  + 1) * 2] = {0};
+
+	snprintf(buf, ISCSI_NAME_LEN, "%s", user);
+	if (pass)
+		snprintf(buf + strlen(buf), ISCSI_NAME_LEN, " %s", pass);
+
+	printf("%sUser %s\n", (auth_dir == AUTH_DIR_INCOMING) ?
+	       "Incoming" : "Outgoing", buf);
+}
+
+static int user_handle_show_user(struct ietadm_req *req, char *user)
+{
+	int err;
+
+	req->rcmnd = C_ACCT_SHOW;
+	strncpy(req->u.acnt.u.user.name, user,
+		sizeof(req->u.acnt.u.user.name) - 1);
+
+	err = ietd_request(req, NULL, 0);
+	if (!err)
+		show_account(req->u.acnt.auth_dir, req->u.acnt.u.user.name,
+			     req->u.acnt.u.user.pass);
+
+	return err;
+}
+
+static int user_handle_show_list(struct ietadm_req *req)
+{
+	int i, err, retry;
+	size_t buf_sz = 0;
+	char *buf;
+
+	req->u.acnt.auth_dir = AUTH_DIR_INCOMING;
+	req->rcmnd = C_ACCT_LIST;
+
+	do {
+		retry = 0;
+
+		buf_sz = buf_sz ? buf_sz : ISCSI_NAME_LEN;
+
+		buf = calloc(buf_sz, sizeof(char *));
+		if (!buf) {
+			fprintf(stderr, "Memory allocation failed\n");
+			return -ENOMEM;
+		}
+
+		req->u.acnt.u.list.alloc_len = buf_sz;
+
+		err = ietd_request(req, buf, buf_sz);
+		if (err) {
+			free(buf);
+			break;
+		}
+
+		if (req->u.acnt.u.list.overflow) {
+			buf_sz = ISCSI_NAME_LEN * (req->u.acnt.u.list.count +
+						   req->u.acnt.u.list.overflow);
+			retry = 1;
+			free(buf);
+			continue;
+		}
+
+		for (i = 0; i < req->u.acnt.u.list.count; i++)
+			show_account(req->u.acnt.auth_dir,
+				     &buf[i * ISCSI_NAME_LEN], NULL);
+
+		if (req->u.acnt.auth_dir == AUTH_DIR_INCOMING) {
+			req->u.acnt.auth_dir = AUTH_DIR_OUTGOING;
+			buf_sz = 0;
+			retry = 1;
+		}
+
+		free(buf);
+
+	} while (retry);
+
+	return err;
+}
+
+static int user_handle_show(struct ietadm_req *req, char *user, char *pass)
+{
+	if (pass)
+		fprintf(stderr, "Ignoring specified password\n");
+
+	if (user)
+		return user_handle_show_user(req, user);
+	else
+		return user_handle_show_list(req);
+}
+
+static int user_handle_new(struct ietadm_req *req, char *user, char *pass)
+{
+	if (!user || !pass) {
+		fprintf(stderr, "Username and password must be specified\n");
+		return -EINVAL;
 	}
 
-	strncpy(req.u.acnt.user, user, sizeof(req.u.acnt.user) - 1);
+	req->rcmnd = C_ACCT_NEW;
+
+	strncpy(req->u.acnt.u.user.name, user,
+		sizeof(req->u.acnt.u.user.name) - 1);
+	strncpy(req->u.acnt.u.user.pass, pass,
+		sizeof(req->u.acnt.u.user.pass) - 1);
+
+	return ietd_request(req, NULL, 0);
+}
+
+static int user_handle_del(struct ietadm_req *req, char *user, char *pass)
+{
+	if (!user) {
+		fprintf(stderr, "Username must be specified\n");
+		return -EINVAL;
+	}
 	if (pass)
-		strncpy(req.u.acnt.pass, pass, sizeof(req.u.acnt.pass) - 1);
+		fprintf(stderr, "Ignoring specified password\n");
 
-	err = ietd_request(&req);
+	req->rcmnd = C_ACCT_DEL;
+
+	strncpy(req->u.acnt.u.user.name, user,
+		sizeof(req->u.acnt.u.user.name) - 1);
+
+	return ietd_request(req, NULL, 0);
+}
+
+static int user_handle(int op, u32 set, u32 tid, char *params)
+{
+	int err = -EINVAL;
+	char *user = NULL, *pass = NULL;
+	struct ietadm_req req;
+	static user_handle_fn_t *user_handle_fn[] = {
+		user_handle_new,
+		user_handle_del,
+		NULL,
+		user_handle_show,
+	}, *fn;
+
+	if (set & ~(SET_TARGET | SET_USER))
+		goto out;
+
+	memset(&req, 0, sizeof(req));
+	req.tid = tid;
+
+	err = parse_user_params(params, &req.u.acnt.auth_dir, &user, &pass);
+	if (err)
+		goto out;
+
+	fn = user_handle_fn[op];
+	if (!fn) {
+		fprintf(stderr, "Unsupported\n");
+		goto out;
+	}
+
+	err = fn(&req, user, pass);
 out:
 	return err;
 }
@@ -494,7 +645,7 @@
 		break;
 	}
 
-	err = ietd_request(&req);
+	err = ietd_request(&req, NULL, 0);
 out:
 	return err;
 }
@@ -518,7 +669,7 @@
 		break;
 	}
 
-	err = ietd_request(&req);
+	err = ietd_request(&req, NULL, 0);
 
 	return err;
 }
Index: usr/plain.c
===================================================================
--- usr/plain.c	(revision 138)
+++ usr/plain.c	(working copy)
@@ -31,7 +31,7 @@
  */
 
 struct user {
-	struct qelem ulist;
+	struct __qelem ulist;
 
 	u32 tid;
 	char *name;
@@ -62,18 +62,18 @@
 	{NULL,},
 };
 
-static struct qelem discovery_users_in = LIST_HEAD_INIT(discovery_users_in);
-static struct qelem discovery_users_out = LIST_HEAD_INIT(discovery_users_out);
+static struct __qelem discovery_users_in = LIST_HEAD_INIT(discovery_users_in);
+static struct __qelem discovery_users_out = LIST_HEAD_INIT(discovery_users_out);
 
 #define HASH_ORDER	4
 #define acct_hash(x)	((x) & ((1 << HASH_ORDER) - 1))
 
-static struct qelem trgt_acct_in[1 << HASH_ORDER];
-static struct qelem trgt_acct_out[1 << HASH_ORDER];
+static struct __qelem trgt_acct_in[1 << HASH_ORDER];
+static struct __qelem trgt_acct_out[1 << HASH_ORDER];
 
-static struct qelem *account_list_get(u32 tid, int dir)
+static struct __qelem *account_list_get(u32 tid, int dir)
 {
-	struct qelem *list = NULL;
+	struct __qelem *list = NULL;
 
 	if (tid) {
 		list = (dir == AUTH_DIR_INCOMING) ?
@@ -125,7 +125,7 @@
 /* Return the first account if the length of name is zero */
 static struct user *account_lookup_by_name(u32 tid, int dir, char *name)
 {
-	struct qelem *list = account_list_get(tid, dir);
+	struct __qelem *list = account_list_get(tid, dir);
 	struct user *user = NULL;
 
 	list_for_each_entry(user, list, ulist) {
@@ -156,6 +156,30 @@
 	return 0;
 }
 
+static int plain_account_list(u32 tid, int dir, u32 *cnt, u32 *overflow,
+			      char *buf, size_t buf_sz)
+{
+	struct __qelem *list = account_list_get(tid, dir);
+	struct user *user;
+
+	*cnt = *overflow = 0;
+
+	if (!list)
+		return -ENOENT;
+
+	list_for_each_entry(user, list, ulist) {
+		if (buf_sz >= ISCSI_NAME_LEN) {
+			strncpy(buf, user->name, ISCSI_NAME_LEN);
+			buf_sz -= ISCSI_NAME_LEN;
+			buf += ISCSI_NAME_LEN;
+			*cnt += 1;
+		} else
+			*overflow += 1;
+	}
+
+	return 0;
+}
+
 static void account_destroy(struct user *user)
 {
 	if (!user)
@@ -196,7 +220,7 @@
 {
 	int err = -ENOMEM;
 	struct user *user;
-	struct qelem *list;
+	struct __qelem *list;
 
 	if (!name || !pass)
 		return -EINVAL;
@@ -220,7 +244,7 @@
 			    " Replacing the old one.\n",
 			    tid ? "target" : "discovery");
 
-		old = (struct user *) list->q_forw;
+		old = list_entry(list->q_forw, struct user, ulist);
 		account_destroy(old);
 	}
 
@@ -617,5 +641,6 @@
 	.account_add		= plain_account_add,
 	.account_del		= plain_account_del,
 	.account_query		= plain_account_query,
+	.account_list		= plain_account_list,
 	.initiator_access	= plain_initiator_access,
 };
Index: usr/ietadm.h
===================================================================
--- usr/ietadm.h	(revision 138)
+++ usr/ietadm.h	(working copy)
@@ -16,8 +16,17 @@
 
 struct msg_acnt {
 	u32 auth_dir;
-	char user[ISCSI_NAME_LEN];
-	char pass[ISCSI_NAME_LEN];
+	union {
+		struct {
+			char name[ISCSI_NAME_LEN];
+			char pass[ISCSI_NAME_LEN];
+		} user;
+		struct {
+			u32 alloc_len;
+			u32 count;
+			u32 overflow;
+		} list;
+	} u;
 };
 
 struct msg_lunit {
@@ -54,6 +63,8 @@
 	C_SYS_DEL,
 	C_SYS_UPDATE,
 	C_SYS_SHOW,
+
+	C_ACCT_LIST,
 };
 
 struct ietadm_req {
Index: usr/config.h
===================================================================
--- usr/config.h	(revision 138)
+++ usr/config.h	(working copy)
@@ -14,6 +14,7 @@
 	int (*account_add) (u32, int, char *, char *);
 	int (*account_del) (u32, int, char *);
 	int (*account_query) (u32, int, char *, char *);
+	int (*account_list) (u32, int, u32 *, u32 *, char *, size_t);
 	int (*initiator_access) (u32, int);
 };
 
Index: usr/isns.c
===================================================================
--- usr/isns.c	(revision 138)
+++ usr/isns.c	(working copy)
@@ -44,12 +44,12 @@
 struct isns_qry_mgmt {
 	char name[ISCSI_NAME_LEN];
 	uint16_t transaction;
-	struct qelem qlist;
+	struct __qelem qlist;
 };
 
 struct isns_initiator {
 	char name[ISCSI_NAME_LEN];
-	struct qelem ilist;
+	struct __qelem ilist;
 };
 
 static LIST_HEAD(qry_list);
Index: usr/iscsid.c
===================================================================
--- usr/iscsid.c	(revision 138)
+++ usr/iscsid.c	(working copy)
@@ -98,7 +98,7 @@
 		}
 		conn->rsp.data = conn->rsp_buffer;
 	}
-	if (conn->rwsize + len > INCOMING_BUFSIZE) {
+	if (conn->rsp.datasize + len > INCOMING_BUFSIZE) {
 		log_warning("Dropping key (%s=%s)", key, value);
 		return;
 	}
Index: usr/log.c
===================================================================
--- usr/log.c	(revision 138)
+++ usr/log.c	(working copy)
@@ -62,15 +62,24 @@
 	}
 }
 
+/* Definition for log_pdu buffer */
+#define BUFFER_SIZE 16
+
+/*
+ * size required for a hex dump of BUFFER_SIZE bytes (' ' + 2 chars = 3 chars
+ * per byte) with a ' |' separator each 4th byte:
+ */
+#define LINE_SIZE (BUFFER_SIZE * 3 + BUFFER_SIZE / 4 * 2 + 1)
+
 static void __dump_line(int level, unsigned char *buf, int *cp)
 {
-	char line[16*3+5], *lp = line;
+	char line[LINE_SIZE], *lp = line;
 	int i, cnt;
 
 	cnt = *cp;
 	if (!cnt)
 		return;
-	for (i = 0; i < 16; i++) {
+	for (i = 0; i < BUFFER_SIZE; i++) {
 		if (i < cnt)
 			lp += sprintf(lp, " %02x", buf[i]);
 		else
@@ -80,7 +89,9 @@
 		if (i >= cnt || !isprint(buf[i]))
 			buf[i] =  ' ';
 	}
-	log_debug(level, "%s %.16s |", line, buf);
+
+	/* buf is not \0-terminated! */
+	log_debug(level, "%s %.*s |", line, BUFFER_SIZE, buf);
 	*cp = 0;
 }
 
@@ -89,7 +100,7 @@
 	int cnt = (*cp)++;
 
 	buf[cnt] = ch;
-	if (cnt == 15)
+	if (cnt == BUFFER_SIZE - 1)
 		__dump_line(level, buf, cp);
 }
 
@@ -98,13 +109,12 @@
 
 void log_pdu(int level, struct PDU *pdu)
 {
-	unsigned char char_buf[16];
+	unsigned char char_buf[BUFFER_SIZE];
 	int char_cnt = 0;
 	unsigned char *buf;
 	int i;
-	return;
 
-	if (log_level <= level)
+	if (log_level < level)
 		return;
 
 	buf = (void *)&pdu->bhs;
Index: usr/iscsid.h
===================================================================
--- usr/iscsid.h	(revision 138)
+++ usr/iscsid.h	(working copy)
@@ -32,7 +32,7 @@
 #define KEY_STATE_DONE		2
 
 struct session {
-	struct qelem slist;
+	struct __qelem slist;
 
 	char *initiator;
 	struct target *target;
@@ -122,9 +122,9 @@
 #define INCOMING_BUFSIZE	8192
 
 struct target {
-	struct qelem tlist;
+	struct __qelem tlist;
 
-	struct qelem sessions_list;
+	struct __qelem sessions_list;
 
 	u32 tid;
 	char name[ISCSI_NAME_LEN];
@@ -133,7 +133,7 @@
 	int max_nr_sessions;
 	int nr_sessions;
 
-	struct qelem isns_head;
+	struct __qelem isns_head;
 };
 
 /* chap.c */
@@ -180,7 +180,7 @@
 extern void session_remove(struct session *session);
 
 /* target.c */
-extern struct qelem targets_list;
+extern struct __qelem targets_list;
 extern int target_add(u32 *, char *);
 extern int target_del(u32);
 extern u32 target_find_by_name(const char *name);
Index: usr/message.c
===================================================================
--- usr/message.c	(revision 138)
+++ usr/message.c	(working copy)
@@ -6,6 +6,7 @@
 
 #include <errno.h>
 #include <stdio.h>
+#include <stdlib.h>
 #include <unistd.h>
 
 #include <sys/socket.h>
@@ -37,7 +38,8 @@
 	return fd;
 }
 
-static void ietadm_request_exec(struct ietadm_req *req, struct ietadm_rsp *rsp)
+static void ietadm_request_exec(struct ietadm_req *req, struct ietadm_rsp *rsp,
+				void **rsp_data, size_t *rsp_data_sz)
 {
 	int err = 0;
 
@@ -99,14 +101,35 @@
 		break;
 
 	case C_ACCT_NEW:
-		err = cops->account_add(req->tid, req->u.acnt.auth_dir, req->u.acnt.user,
-					req->u.acnt.pass);
+		err = cops->account_add(req->tid, req->u.acnt.auth_dir,
+					req->u.acnt.u.user.name,
+					req->u.acnt.u.user.pass);
 		break;
 	case C_ACCT_DEL:
-		err = cops->account_del(req->tid, req->u.acnt.auth_dir, req->u.acnt.user);
+		err = cops->account_del(req->tid, req->u.acnt.auth_dir,
+					req->u.acnt.u.user.name);
 		break;
+	case C_ACCT_LIST:
+		*rsp_data = malloc(req->u.acnt.u.list.alloc_len);
+		if (!*rsp_data) {
+			err = -ENOMEM;
+			break;
+		}
+
+		*rsp_data_sz = req->u.acnt.u.list.alloc_len;
+		memset(*rsp_data, 0x0, *rsp_data_sz);
+
+		err = cops->account_list(req->tid, req->u.acnt.auth_dir,
+					 &req->u.acnt.u.list.count,
+					 &req->u.acnt.u.list.overflow,
+					 *rsp_data, *rsp_data_sz);
+		break;
 	case C_ACCT_UPDATE:
+		break;
 	case C_ACCT_SHOW:
+		err = cops->account_query(req->tid, req->u.acnt.auth_dir,
+					  req->u.acnt.u.user.name,
+					  req->u.acnt.u.user.pass);
 		break;
 	case C_SYS_NEW:
 		break;
@@ -132,7 +155,9 @@
 	socklen_t len;
 	struct ietadm_req req;
 	struct ietadm_rsp rsp;
-	struct iovec iov[2];
+	struct iovec iov[3];
+	void *rsp_data = NULL;
+	size_t rsp_data_sz;
 
 	memset(&rsp, 0, sizeof(rsp));
 	len = sizeof(addr);
@@ -162,17 +187,22 @@
 		goto out;
 	}
 
-	ietadm_request_exec(&req, &rsp);
+	ietadm_request_exec(&req, &rsp, &rsp_data, &rsp_data_sz);
 
 send:
 	iov[0].iov_base = &req;
 	iov[0].iov_len = sizeof(req);
 	iov[1].iov_base = &rsp;
 	iov[1].iov_len = sizeof(rsp);
+	iov[2].iov_base = rsp.err ? NULL : rsp_data;
+	iov[2].iov_len = iov[2].iov_base ? rsp_data_sz : 0;
 
-	err = writev(fd, iov, 2);
+	err = writev(fd, iov, 2 + !!iov[2].iov_len);
 out:
 	if (fd > 0)
 		close(fd);
+	if (rsp_data)
+		free(rsp_data);
+
 	return err;
 }
Index: usr/target.c
===================================================================
--- usr/target.c	(revision 138)
+++ usr/target.c	(working copy)
@@ -14,7 +14,7 @@
 
 #include "iscsid.h"
 
-struct qelem targets_list = LIST_HEAD_INIT(targets_list);
+struct __qelem targets_list = LIST_HEAD_INIT(targets_list);
 
 void target_list_build(struct connection *conn, char *addr, char *name)
 {
Index: usr/Makefile
===================================================================
--- usr/Makefile	(revision 138)
+++ usr/Makefile	(working copy)
@@ -1,4 +1,5 @@
 CFLAGS += -O2 -fno-inline -Wall -Wstrict-prototypes -g -I../include
+CFLAGS += -D_GNU_SOURCE # required for glibc >= 2.8
 PROGRAMS = ietd ietadm
 LIBS = -lcrypto
 
Index: Makefile
===================================================================
--- Makefile	(revision 138)
+++ Makefile	(working copy)
@@ -51,6 +51,11 @@
 # base first the earlier patch sets will not need to be modified.
 #
 
+# Compatibility patch for kernels >= 2.6.22 and <= 2.6.23
+ifeq ($(call kver_le,2,6,23),1)
+	PATCHES := $(PATCHES) compat-2.6.22-2.6.23.patch
+endif
+
 # Compatibility patch for kernels >= 2.6.19 and <= 2.6.21
 ifeq ($(call kver_le,2,6,21),1)
 	PATCHES := $(PATCHES) compat-2.6.19-2.6.21.patch