Subject: Added compression handling to vnd driver
To: None <tech-kern@NetBSD.org>
From: Cliff Wright <cliff@snipe444.org>
List: tech-kern
Date: 03/29/2005 11:01:21
 I wanted to have compresion handling similar to what Knoppix has for
live CD creation. So I modified the vnd driver to also have this
 capability. I am showing what I have done in case others have interest.
 Some questions have come up. The driver code has NOTE 1 which says
"This uses the VOP_BMAP/VOP_STRATEGY interface to the vnode
instead of a simple VOP_RDWR.
We do this to avoid distorting the local buffer cache."
I used vn_rdwr because it was simpler, so what distortion am I causing?
  The Knoppix cloop method is to break the file up into blocks 
(64k by default),
compress each block, and save indexes into the compressed file of where
these blocks start, and place these indexes near the begining of the
compressed file. This allows for quick indexing into the compressed file.
However it does mean a special file format is used. The cloop driver
places all of these indexes in a memory buffer. I used the same method,
but wonder if just reading in a few at a time would be better? The
cloop driver also did a one time allocation of a buffer to hold the
compressed data block. I also used this method but wonder if the
use of the buffer pool instead might be better.
  I added a flag to vnconfig (-z) to indicate the file is a compressed
file. Although autodection of file format would be nice, I don't
think this could be compatible with Knoppix cloop files as their
preamble does not appear to be unique enough. Also note this is all for
readonly use of the compressed file.
 Patches to 2.0 follow:

--- src/usr.sbin/vnconfig/vnconfig.c.orig	2004-01-25 13:49:04.000000000 -0800
+++ src/usr.sbin/vnconfig/vnconfig.c	2005-03-28 13:58:41.000000000 -0800
@@ -139,6 +139,7 @@
 int	verbose = 0;
 int	readonly = 0;
 int	force = 0;
+int	compressed = 0;
 char	*tabname;
 
 int	config __P((char *, char *, char *, int));
@@ -154,7 +155,7 @@
 {
 	int ch, rv, action = VND_CONFIG;
 
-	while ((ch = getopt(argc, argv, "Fcf:lrt:uv")) != -1) {
+	while ((ch = getopt(argc, argv, "Fcf:lrt:uvz")) != -1) {
 		switch (ch) {
 		case 'F':
 			force = 1;
@@ -181,6 +182,10 @@
 		case 'v':
 			verbose = 1;
 			break;
+		case 'z':
+			compressed = 1;
+			readonly = 1;
+			break;
 		default:
 		case '?':
 			usage();
@@ -314,6 +319,9 @@
 	if (readonly)
 		vndio.vnd_flags |= VNDIOF_READONLY;
 
+	if (compressed)
+		vndio.vnd_flags |= 0x200;	/* VNF_COMP, cliff */
+
 	/*
 	 * Clear (un-configure) the device
 	 */
--- src/sys/dev/vndvar.h.orig	2003-08-07 09:30:52.000000000 -0700
+++ src/sys/dev/vndvar.h	2005-03-28 16:58:17.000000000 -0800
@@ -162,6 +162,13 @@
 	struct vndgeom	 sc_geom;	/* virtual geometry */
 	struct pool	 sc_vxpool;	/* vndxfer pool */
 	struct pool	 sc_vbpool;	/* vndbuf pool */
+	u_int32_t	 sc_comp_blksz;	/* precompressed block size */
+	u_int32_t	 sc_comp_numoffs;/* count of compressed block offsets */
+	u_int64_t	 *sc_comp_offsets;/* file idx's to compressed blocks */
+	unsigned char	 *sc_comp_buff;	/* compressed data buffer */
+	unsigned char	 *sc_comp_decombuf;/* decompressed data buffer */
+	int32_t		 sc_comp_buffblk;/* current decompressed block */
+	z_stream	 sc_comp_stream;/* decompress descriptor */
 };
 #endif
 
@@ -175,6 +182,15 @@
 #define	VNF_READONLY	0x040	/* unit is read-only */
 #define	VNF_KLABEL	0x080	/* keep label on close */
 #define	VNF_VLABEL	0x100	/* label is valid */
+#define VNF_COMP	0x200	/* file is compressed */
+
+/* structure of header in a compressed file */
+struct comp_header
+{
+	char preamble[128];
+	u_int32_t block_size;
+	u_int32_t num_blocks;
+};
 
 /*
  * A simple structure for describing which vnd units are in use.
--- src/sys/dev/vnd.c.orig	2004-01-25 10:06:48.000000000 -0800
+++ src/sys/dev/vnd.c	2005-03-29 10:10:57.000000000 -0800
@@ -156,6 +156,7 @@
 #include <sys/file.h>
 #include <sys/uio.h>
 #include <sys/conf.h>
+#include <net/zlib.h>
 
 #include <miscfs/specfs/specdev.h>
 
@@ -207,6 +208,7 @@
 
 void	vndclear __P((struct vnd_softc *, int));
 void	vndstart __P((struct vnd_softc *));
+void	compstrategy __P((struct buf *, off_t));
 int	vndsetcred __P((struct vnd_softc *, struct ucred *));
 void	vndthrottle __P((struct vnd_softc *, struct vnode *));
 void	vndiodone __P((struct buf *));
@@ -219,6 +221,8 @@
 
 static	int vndlock __P((struct vnd_softc *));
 static	void vndunlock __P((struct vnd_softc *));
+static	void *vnd_alloc __P((void *, u_int, u_int));
+static	void vnd_free __P((void *, void *));
 
 dev_type_open(vndopen);
 dev_type_close(vndclose);
@@ -263,6 +267,9 @@
 
 	for (i = 0; i < numvnd; i++) {
 		vnd_softc[i].sc_unit = i;
+		vnd_softc[i].sc_comp_offsets = NULL;
+		vnd_softc[i].sc_comp_buff = NULL;
+		vnd_softc[i].sc_comp_decombuf = NULL;
 		bufq_alloc(&vnd_softc[i].sc_tab,
 		    BUFQ_DISKSORT|BUFQ_SORT_RAWBLOCK);
 	}
@@ -483,6 +490,12 @@
 		bp->b_flags |= B_ERROR;
 		goto done;
 	}
+	/* handle a compressed read */
+	if((bp->b_flags & B_READ) && (vnd->sc_flags & VNF_COMP)) {
+		compstrategy(bp, bn);
+		goto done;
+	}
+
  	bsize = vnd->sc_vp->v_mount->mnt_stat.f_iosize;
 	addr = bp->b_data;
 	flags = (bp->b_flags & (B_READ|B_ASYNC)) | B_CALL;
@@ -726,6 +739,98 @@
 	splx(s);
 }
 
+/* compressed file read */
+void
+compstrategy(bp, bn)
+	struct buf *bp;
+	off_t bn;
+{
+	int error;
+	int unit = vndunit(bp->b_dev);
+	struct vnd_softc *vnd = &vnd_softc[unit];
+	u_int32_t comp_block;
+	struct uio auio;
+	caddr_t addr;
+
+	/* set up constants for data move */
+	auio.uio_rw = UIO_READ;
+	auio.uio_segflg = bp->b_flags & B_PHYS ? UIO_USERSPACE : UIO_SYSSPACE;
+	auio.uio_procp = bp->b_proc;
+
+	/* read, and transfer the data */
+	addr = bp->b_data;
+	while(bp->b_resid > 0) {
+		unsigned length;
+		size_t length_in_buffer;
+		u_int32_t offset_in_buffer;
+		struct iovec aiov;
+
+		/* calculate the compressed block number */
+		comp_block = bn / (off_t)vnd->sc_comp_blksz;
+
+		/* check for good block number */
+		if(comp_block >= vnd->sc_comp_numoffs) {
+			bp->b_error = EINVAL;
+			bp->b_flags |= B_ERROR;
+			return;
+		}
+
+		/* read in the compressed block, if not in buffer */
+		if (comp_block != vnd->sc_comp_buffblk) {
+			length = vnd->sc_comp_offsets[comp_block + 1] -
+				 vnd->sc_comp_offsets[comp_block];
+			error = vn_rdwr(UIO_READ, vnd->sc_vp, vnd->sc_comp_buff,
+			  length, vnd->sc_comp_offsets[comp_block],
+			  UIO_SYSSPACE, IO_UNIT, vnd->sc_cred, NULL, NULL);
+			if(error) {
+				bp->b_error = error;
+				bp->b_flags |= B_ERROR;
+				return;
+			}
+			/* uncompress the buffer */
+			vnd->sc_comp_stream.next_in = vnd->sc_comp_buff;
+			vnd->sc_comp_stream.avail_in = length;
+			vnd->sc_comp_stream.next_out = vnd->sc_comp_decombuf;
+			vnd->sc_comp_stream.avail_out = vnd->sc_comp_blksz;
+			inflateReset(&vnd->sc_comp_stream);
+			error = inflate(&vnd->sc_comp_stream, Z_FINISH);
+			if(error != Z_STREAM_END) {
+				if(vnd->sc_comp_stream.msg)
+					printf("%s: compressed file, %s\n",
+					  vnd->sc_xname,
+					  vnd->sc_comp_stream.msg);
+				bp->b_error = EBADMSG;
+				bp->b_flags |= B_ERROR;
+				return;
+			}
+			vnd->sc_comp_buffblk = comp_block;
+		}
+
+		/* transfer the usable uncompressed data */
+		offset_in_buffer = bn % (off_t)vnd->sc_comp_blksz;
+		length_in_buffer = vnd->sc_comp_blksz - offset_in_buffer;
+		if(length_in_buffer > bp->b_resid)
+			length_in_buffer = bp->b_resid;
+		auio.uio_iov = &aiov;
+		auio.uio_iovcnt = 1;
+		aiov.iov_base = addr;
+		aiov.iov_len = length_in_buffer;
+		auio.uio_resid = aiov.iov_len;
+		auio.uio_offset = 0;
+		error = uiomove(vnd->sc_comp_decombuf + offset_in_buffer,
+		  length_in_buffer, &auio);
+		if(error) {
+			bp->b_error = error;
+			bp->b_flags |= B_ERROR;
+			return;
+		}
+
+		bn += length_in_buffer;
+		addr += length_in_buffer;
+		bp->b_resid -= length_in_buffer;
+	}
+}
+
 /* ARGSUSED */
 int
 vndread(dev, uio, flags)
@@ -859,11 +964,115 @@
 		if ((error = vn_open(&nd, fflags, 0)) != 0)
 			goto unlock_and_exit;
 		error = VOP_GETATTR(nd.ni_vp, &vattr, p->p_ucred, p);
-		VOP_UNLOCK(nd.ni_vp, 0);
 		if (!error && nd.ni_vp->v_type != VREG)
 			error = EOPNOTSUPP;
-		if (error)
+		if (error) {
+			VOP_UNLOCK(nd.ni_vp, 0);
 			goto close_and_exit;
+		}
+
+		/* If using a compressed file, initialize its info */
+		if (vio->vnd_flags & VNF_COMP) {
+			struct comp_header *ch;
+			int error;
+			int i;
+			u_int32_t comp_size;
+			u_int32_t comp_maxsize;
+
+			/* allocate space for compresed file header */
+			ch = malloc(sizeof(struct comp_header),
+			M_TEMP, M_WAITOK);
+
+			/* read compressed file header */
+			error = vn_rdwr(UIO_READ, nd.ni_vp, (caddr_t)ch,
+			  sizeof(struct comp_header), 0, UIO_SYSSPACE,
+			  IO_UNIT|IO_NODELOCKED, p->p_ucred, NULL, NULL);
+			if(error) {
+				free(ch, M_TEMP);
+				VOP_UNLOCK(nd.ni_vp, 0);
+				goto close_and_exit;
+			}
+
+			/* save some header info */
+			vnd->sc_comp_blksz = ntohl(ch->block_size);
+			/* note last offset is the file byte size */
+			vnd->sc_comp_numoffs = ntohl(ch->num_blocks)+1;
+			free(ch, M_TEMP);
+			if(vnd->sc_comp_blksz % DEV_BSIZE !=0) {
+				VOP_UNLOCK(nd.ni_vp, 0);
+				error = EINVAL;
+				goto close_and_exit;
+			}
+			if(sizeof(struct comp_header) +
+			  sizeof(u_int64_t) * vnd->sc_comp_numoffs >
+			  vattr.va_size) {
+				VOP_UNLOCK(nd.ni_vp, 0);
+				error = EINVAL;
+				goto close_and_exit;
+			}
+
+			/* set decompressed file size */
+			vattr.va_size =
+			  (vnd->sc_comp_numoffs - 1) * vnd->sc_comp_blksz;
+
+			/* allocate space for all the compressed offsets */
+			vnd->sc_comp_offsets =
+			malloc(sizeof(u_int64_t) * vnd->sc_comp_numoffs,
+			M_DEVBUF, M_WAITOK);
+
+			/* read in the offsets */
+			error = vn_rdwr(UIO_READ, nd.ni_vp,
+			  (caddr_t)vnd->sc_comp_offsets,
+			  sizeof(u_int64_t) * vnd->sc_comp_numoffs,
+			  sizeof(struct comp_header), UIO_SYSSPACE,
+			  IO_UNIT|IO_NODELOCKED, p->p_ucred, NULL, NULL);
+			if(error) {
+				VOP_UNLOCK(nd.ni_vp, 0);
+				goto close_and_exit;
+			}
+			/*
+			 * find largest block size (used for allocation limit).
+			 * Also convert offset to native byte order.
+			 */
+			comp_maxsize = 0;
+			for (i = 0; i < vnd->sc_comp_numoffs - 1; i++) {
+				vnd->sc_comp_offsets[i] =
+				  be64toh(vnd->sc_comp_offsets[i]);
+				comp_size = be64toh(vnd->sc_comp_offsets[i + 1])
+				  - vnd->sc_comp_offsets[i];
+				if (comp_size > comp_maxsize)
+					comp_maxsize = comp_size;
+			}
+			vnd->sc_comp_offsets[vnd->sc_comp_numoffs - 1] =
+			be64toh(vnd->sc_comp_offsets[vnd->sc_comp_numoffs - 1]);
+
+			/* create compressed data buffer */
+			vnd->sc_comp_buff = malloc(comp_maxsize,
+			  M_DEVBUF, M_WAITOK);
+
+			/* create decompressed buffer */
+			vnd->sc_comp_decombuf = malloc(vnd->sc_comp_blksz,
+			  M_DEVBUF, M_WAITOK);
+			vnd->sc_comp_buffblk = -1;
+
+			/* Initialize decompress stream */
+			bzero(&vnd->sc_comp_stream, sizeof(z_stream));
+			vnd->sc_comp_stream.zalloc = vnd_alloc;
+			vnd->sc_comp_stream.zfree = vnd_free;
+			error = inflateInit2(&vnd->sc_comp_stream, MAX_WBITS);
+			if(error) {
+				if(vnd->sc_comp_stream.msg)
+					printf("vnd%d: compressed file, %s\n",
+					  unit, vnd->sc_comp_stream.msg);
+				VOP_UNLOCK(nd.ni_vp, 0);
+				error = EINVAL;
+				goto close_and_exit;
+			}
+
+			vnd->sc_flags |= VNF_COMP | VNF_READONLY;
+		}
+
+		VOP_UNLOCK(nd.ni_vp, 0);
 		vnd->sc_vp = nd.ni_vp;
 		vnd->sc_size = btodb(vattr.va_size);	/* note truncation */
 
@@ -964,6 +1173,19 @@
 close_and_exit:
 		(void) vn_close(nd.ni_vp, fflags, p->p_ucred, p);
 unlock_and_exit:
+		/* free any allocated memory (for compressed file) */
+		if(vnd->sc_comp_offsets) {
+			free(vnd->sc_comp_offsets, M_DEVBUF);
+			vnd->sc_comp_offsets = NULL;
+		}
+		if(vnd->sc_comp_buff) {
+			free(vnd->sc_comp_buff, M_DEVBUF);
+			vnd->sc_comp_buff = NULL;
+		}
+		if(vnd->sc_comp_decombuf) {
+			free(vnd->sc_comp_decombuf, M_DEVBUF);
+			vnd->sc_comp_decombuf = NULL;
+		}
 		vndunlock(vnd);
 		return (error);
 
@@ -1243,7 +1465,22 @@
 
 	if ((vnd->sc_flags & VNF_READONLY) == 0)
 		fflags |= FWRITE;
-	vnd->sc_flags &= ~(VNF_INITED | VNF_READONLY | VNF_VLABEL);
+	/* free the compressed file buffers */
+	if(vnd->sc_flags & VNF_COMP) {
+		if(vnd->sc_comp_offsets) {
+			free(vnd->sc_comp_offsets, M_DEVBUF);
+			vnd->sc_comp_offsets = NULL;
+		}
+		if(vnd->sc_comp_buff) {
+			free(vnd->sc_comp_buff, M_DEVBUF);
+			vnd->sc_comp_buff = NULL;
+		}
+		if(vnd->sc_comp_decombuf) {
+			free(vnd->sc_comp_decombuf, M_DEVBUF);
+			vnd->sc_comp_decombuf = NULL;
+		}
+	}
+	vnd->sc_flags &= ~(VNF_INITED | VNF_READONLY | VNF_VLABEL | VNF_COMP);
 	if (vp == (struct vnode *)0)
 		panic("vndioctl: null vp");
 	(void) vn_close(vp, fflags, vnd->sc_cred, p);
@@ -1428,3 +1665,23 @@
 		wakeup(sc);
 	}
 }
+
+/* compression memory allocation routines */
+static void *
+vnd_alloc(aux, items, siz)
+	void *aux;
+	u_int items;
+	u_int siz;
+{
+	void *ptr;
+	ptr = malloc(items * siz, M_TEMP, M_NOWAIT);
+	return ptr;
+}
+
+static void
+vnd_free(aux, ptr)
+	void *aux;
+	void *ptr;
+{
+	free(ptr, M_TEMP);
+}