1/*-
2 * Copyright (c) 2010-2012 Aleksandr Rybalko
3 * Copyright (c) 2004 Max Khon
4 * All rights reserved.
5 *
6 * Redistribution and use in source and binary forms, with or without
7 * modification, are permitted provided that the following conditions
8 * are met:
9 * 1. Redistributions of source code must retain the above copyright
10 *    notice, this list of conditions and the following disclaimer.
11 * 2. Redistributions in binary form must reproduce the above copyright
12 *    notice, this list of conditions and the following disclaimer in the
13 *    documentation and/or other materials provided with the distribution.
14 *
15 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
16 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
17 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
18 * ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
19 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
20 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
21 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
22 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
23 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
24 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
25 * SUCH DAMAGE.
26 */
27
28/*
29 * GEOM UNCOMPRESS module - simple decompression module for use with read-only
30 * copressed images maked by mkuzip(8) or mkulzma(8) utilities.
31 *
32 * To enable module in kernel config, put this line:
33 * device	geom_uncompress
34 */
35
36#include <sys/cdefs.h>
37__FBSDID("$FreeBSD$");
38
39#include <sys/param.h>
40#include <sys/bio.h>
41#include <sys/endian.h>
42#include <sys/errno.h>
43#include <sys/kernel.h>
44#include <sys/lock.h>
45#include <sys/mutex.h>
46#include <sys/malloc.h>
47#include <sys/systm.h>
48
49#include <geom/geom.h>
50
51#include <net/zlib.h>
52#include <contrib/xz-embedded/linux/include/linux/xz.h>
53
54#ifdef GEOM_UNCOMPRESS_DEBUG
55#define	DPRINTF(a)	printf a
56extern int g_debugflags;
57#else
58#define	DPRINTF(a)
59#endif
60
61static MALLOC_DEFINE(M_GEOM_UNCOMPRESS, "geom_uncompress",
62    "GEOM UNCOMPRESS data structures");
63
64#define	UNCOMPRESS_CLASS_NAME	"UNCOMPRESS"
65#define	GEOM_UZIP_MAJVER '2'
66#define	GEOM_ULZMA_MAJVER '3'
67
68/*
69 * Maximum allowed valid block size (to prevent foot-shooting)
70 */
71#define	MAX_BLKSZ	(MAXPHYS)
72
73/*
74 * Integer values (block size, number of blocks, offsets)
75 * are stored in big-endian (network) order on disk and struct cloop_header
76 * and in native order in struct g_uncompress_softc
77 */
78
79#define	CLOOP_MAGIC_LEN	128
80static char CLOOP_MAGIC_START[] = "#!/bin/sh\n";
81
82struct cloop_header {
83	char magic[CLOOP_MAGIC_LEN];	/* cloop magic */
84	uint32_t blksz;			/* block size */
85	uint32_t nblocks;		/* number of blocks */
86};
87
88struct g_uncompress_softc {
89	uint32_t blksz;			/* block size */
90	uint32_t nblocks;		/* number of blocks */
91	uint64_t *offsets;
92	enum {
93		GEOM_UZIP = 1,
94		GEOM_ULZMA
95	} type;
96
97	struct mtx last_mtx;
98	uint32_t last_blk;		/* last blk no */
99	char *last_buf;			/* last blk data */
100	int req_total;			/* total requests */
101	int req_cached;			/* cached requests */
102
103	/* XZ decoder structs */
104	struct xz_buf *b;
105	struct xz_dec *s;
106	z_stream *zs;
107};
108
109static void
110g_uncompress_softc_free(struct g_uncompress_softc *sc, struct g_geom *gp)
111{
112
113	if (gp != NULL) {
114		printf("%s: %d requests, %d cached\n",
115		    gp->name, sc->req_total, sc->req_cached);
116	}
117	if (sc->offsets != NULL) {
118		free(sc->offsets, M_GEOM_UNCOMPRESS);
119		sc->offsets = 0;
120	}
121
122	switch (sc->type) {
123	case GEOM_ULZMA:
124		if (sc->b) {
125			free(sc->b, M_GEOM_UNCOMPRESS);
126			sc->b = 0;
127		}
128		if (sc->s) {
129			xz_dec_end(sc->s);
130			sc->s = 0;
131		}
132		break;
133	case GEOM_UZIP:
134		if (sc->zs) {
135			inflateEnd(sc->zs);
136			free(sc->zs, M_GEOM_UNCOMPRESS);
137			sc->zs = 0;
138		}
139		break;
140	}
141
142	mtx_destroy(&sc->last_mtx);
143	free(sc->last_buf, M_GEOM_UNCOMPRESS);
144	free(sc, M_GEOM_UNCOMPRESS);
145}
146
147static void *
148z_alloc(void *nil, u_int type, u_int size)
149{
150	void *ptr;
151
152	ptr = malloc(type * size, M_GEOM_UNCOMPRESS, M_NOWAIT);
153	return (ptr);
154}
155
156static void
157z_free(void *nil, void *ptr)
158{
159
160	free(ptr, M_GEOM_UNCOMPRESS);
161}
162
163static void
164g_uncompress_done(struct bio *bp)
165{
166	struct g_uncompress_softc *sc;
167	struct g_provider *pp, *pp2;
168	struct g_consumer *cp;
169	struct g_geom *gp;
170	struct bio *bp2;
171	uint32_t start_blk, i;
172	off_t pos, upos;
173	size_t bsize;
174	int err;
175
176	err = 0;
177	bp2 = bp->bio_parent;
178	pp = bp2->bio_to;
179	gp = pp->geom;
180	cp = LIST_FIRST(&gp->consumer);
181	pp2 = cp->provider;
182	sc = gp->softc;
183	DPRINTF(("%s: done\n", gp->name));
184
185	bp2->bio_error = bp->bio_error;
186	if (bp2->bio_error != 0)
187		goto done;
188
189	/*
190	 * Example:
191	 * Uncompressed block size = 65536
192	 * User request: 65540-261632
193	 * (4 uncompressed blocks -4B at start, -512B at end)
194	 *
195	 * We have 512(secsize)*63(nsec) = 32256B at offset 1024
196	 * From:  1024  bp->bio_offset = 1024
197	 * To:   33280  bp->bio_length = 33280 - 1024 = 32256
198	 * Compressed blocks: 0-1020, 1020-1080, 1080-4845, 4845-12444,
199	 * 	12444-33210, 33210-44100, ...
200	 *
201	 * Get start_blk from user request:
202	 * start_blk = bp2->bio_offset / 65536 = 65540/65536 = 1
203	 * bsize (block size of parent) = pp2->sectorsize (Now is 4B)
204	 * pos(in stream from parent) = sc->offsets[start_blk] % bsize =
205	 *    = sc->offsets[1] % 4 = 1020 % 4 = 0
206	 */
207
208	/*
209	 * Uncompress data.
210	 */
211	start_blk = bp2->bio_offset / sc->blksz;
212	bsize = pp2->sectorsize;
213	pos = sc->offsets[start_blk] % bsize;
214	upos = 0;
215
216	DPRINTF(("%s: done: bio_length %lld bio_completed %lld start_blk %d, "
217		"pos %lld, upos %lld (%lld, %d, %d)\n",
218	    gp->name, bp->bio_length, bp->bio_completed, start_blk, pos, upos,
219	    bp2->bio_offset, sc->blksz, bsize));
220
221	for (i = start_blk; upos < bp2->bio_length; i++) {
222		off_t len, dlen, ulen, uoff;
223
224		uoff = i == start_blk ? bp2->bio_offset % sc->blksz : 0;
225		ulen = MIN(sc->blksz - uoff, bp2->bio_length - upos);
226		dlen = len = sc->offsets[i + 1] - sc->offsets[i];
227
228		DPRINTF(("%s: done: inflate block %d, start %lld, end %lld "
229			"len %lld\n",
230		    gp->name, i, sc->offsets[i], sc->offsets[i + 1], len));
231
232		if (len == 0) {
233			/* All zero block: no cache update */
234			bzero(bp2->bio_data + upos, ulen);
235			upos += ulen;
236			bp2->bio_completed += ulen;
237			continue;
238		}
239
240		mtx_lock(&sc->last_mtx);
241
242#ifdef GEOM_UNCOMPRESS_DEBUG
243		if (g_debugflags & 32)
244			hexdump(bp->bio_data + pos, dlen, 0, 0);
245#endif
246
247		switch (sc->type) {
248		case GEOM_ULZMA:
249			sc->b->in = bp->bio_data + pos;
250			sc->b->out = sc->last_buf;
251			sc->b->in_pos = sc->b->out_pos = 0;
252			sc->b->in_size = dlen;
253			sc->b->out_size = (size_t)-1;
254
255			err = (xz_dec_run(sc->s, sc->b) != XZ_STREAM_END) ?
256			    1 : 0;
257			/* TODO decoder recovery, if needed */
258			break;
259		case GEOM_UZIP:
260			sc->zs->next_in = bp->bio_data + pos;
261			sc->zs->avail_in = dlen;
262			sc->zs->next_out = sc->last_buf;
263			sc->zs->avail_out = sc->blksz;
264
265			err = (inflate(sc->zs, Z_FINISH) != Z_STREAM_END) ?
266			    1 : 0;
267			if ((err) && (inflateReset(sc->zs) != Z_OK))
268				printf("%s: UZIP decoder reset failed\n",
269				     gp->name);
270			break;
271		}
272
273		if (err) {
274			sc->last_blk = -1;
275			mtx_unlock(&sc->last_mtx);
276			DPRINTF(("%s: done: inflate failed, code=%d\n",
277			    gp->name, err));
278			bp2->bio_error = EIO;
279			goto done;
280		}
281
282#ifdef GEOM_UNCOMPRESS_DEBUG
283		if (g_debugflags & 32)
284			hexdump(sc->last_buf, sc->b->out_size, 0, 0);
285#endif
286
287		sc->last_blk = i;
288		DPRINTF(("%s: done: inflated \n", gp->name));
289		memcpy(bp2->bio_data + upos, sc->last_buf + uoff, ulen);
290		mtx_unlock(&sc->last_mtx);
291
292		pos += len;
293		upos += ulen;
294		bp2->bio_completed += ulen;
295	}
296
297done:
298	/*
299	 * Finish processing the request.
300	 */
301	DPRINTF(("%s: done: (%d, %lld, %ld)\n",
302	    gp->name, bp2->bio_error, bp2->bio_completed, bp2->bio_resid));
303	free(bp->bio_data, M_GEOM_UNCOMPRESS);
304	g_destroy_bio(bp);
305	g_io_deliver(bp2, bp2->bio_error);
306}
307
308static void
309g_uncompress_start(struct bio *bp)
310{
311	struct g_uncompress_softc *sc;
312	struct g_provider *pp, *pp2;
313	struct g_consumer *cp;
314	struct g_geom *gp;
315	struct bio *bp2;
316	uint32_t start_blk, end_blk;
317	size_t bsize;
318
319
320	pp = bp->bio_to;
321	gp = pp->geom;
322	DPRINTF(("%s: start (%s) to %s off=%lld len=%lld\n", gp->name,
323		(bp->bio_cmd==BIO_READ) ? "BIO_READ" : "BIO_WRITE*",
324		pp->name, bp->bio_offset, bp->bio_length));
325
326	if (bp->bio_cmd != BIO_READ) {
327		g_io_deliver(bp, EOPNOTSUPP);
328		return;
329	}
330
331	cp = LIST_FIRST(&gp->consumer);
332	pp2 = cp->provider;
333	sc = gp->softc;
334
335	start_blk = bp->bio_offset / sc->blksz;
336	end_blk   = howmany(bp->bio_offset + bp->bio_length, sc->blksz);
337	KASSERT(start_blk < sc->nblocks,
338		("start_blk out of range"));
339	KASSERT(end_blk <= sc->nblocks,
340		("end_blk out of range"));
341
342	sc->req_total++;
343	if (start_blk + 1 == end_blk) {
344		mtx_lock(&sc->last_mtx);
345		if (start_blk == sc->last_blk) {
346			off_t uoff;
347
348			uoff = bp->bio_offset % sc->blksz;
349			KASSERT(bp->bio_length <= sc->blksz - uoff,
350			    ("cached data error"));
351			memcpy(bp->bio_data, sc->last_buf + uoff,
352			    bp->bio_length);
353			sc->req_cached++;
354			mtx_unlock(&sc->last_mtx);
355
356			DPRINTF(("%s: start: cached 0 + %lld, "
357			    "%lld + 0 + %lld\n",
358			    gp->name, bp->bio_length, uoff, bp->bio_length));
359			bp->bio_completed = bp->bio_length;
360			g_io_deliver(bp, 0);
361			return;
362		}
363		mtx_unlock(&sc->last_mtx);
364	}
365
366	bp2 = g_clone_bio(bp);
367	if (bp2 == NULL) {
368		g_io_deliver(bp, ENOMEM);
369		return;
370	}
371	DPRINTF(("%s: start (%d..%d), %s: %d + %llu, %s: %d + %llu\n",
372	    gp->name, start_blk, end_blk,
373	    pp->name, pp->sectorsize, pp->mediasize,
374	    pp2->name, pp2->sectorsize, pp2->mediasize));
375
376	bsize = pp2->sectorsize;
377
378	bp2->bio_done = g_uncompress_done;
379	bp2->bio_offset = rounddown(sc->offsets[start_blk],bsize);
380	bp2->bio_length = roundup(sc->offsets[end_blk],bsize) -
381	    bp2->bio_offset;
382	bp2->bio_data = malloc(bp2->bio_length, M_GEOM_UNCOMPRESS, M_NOWAIT);
383
384	DPRINTF(("%s: start %lld + %lld -> %lld + %lld -> %lld + %lld\n",
385	    gp->name,
386	    bp->bio_offset, bp->bio_length,
387	    sc->offsets[start_blk],
388	    sc->offsets[end_blk] - sc->offsets[start_blk],
389	    bp2->bio_offset, bp2->bio_length));
390
391	if (bp2->bio_data == NULL) {
392		g_destroy_bio(bp2);
393		g_io_deliver(bp, ENOMEM);
394		return;
395	}
396
397	g_io_request(bp2, cp);
398	DPRINTF(("%s: start ok\n", gp->name));
399}
400
401static void
402g_uncompress_orphan(struct g_consumer *cp)
403{
404	struct g_geom *gp;
405
406	g_trace(G_T_TOPOLOGY, "%s(%p/%s)", __func__, cp,
407		cp->provider->name);
408	g_topology_assert();
409
410	gp = cp->geom;
411	g_uncompress_softc_free(gp->softc, gp);
412	gp->softc = NULL;
413	g_wither_geom(gp, ENXIO);
414}
415
416static int
417g_uncompress_access(struct g_provider *pp, int dr, int dw, int de)
418{
419	struct g_consumer *cp;
420	struct g_geom *gp;
421
422	gp = pp->geom;
423	cp = LIST_FIRST(&gp->consumer);
424	KASSERT (cp != NULL, ("g_uncompress_access but no consumer"));
425
426	if (cp->acw + dw > 0)
427		return (EROFS);
428
429	return (g_access(cp, dr, dw, de));
430}
431
432static void
433g_uncompress_spoiled(struct g_consumer *cp)
434{
435	struct g_geom *gp;
436
437	gp = cp->geom;
438	g_trace(G_T_TOPOLOGY, "%s(%p/%s)", __func__, cp, gp->name);
439	g_topology_assert();
440
441	g_uncompress_softc_free(gp->softc, gp);
442	gp->softc = NULL;
443	g_wither_geom(gp, ENXIO);
444}
445
446static struct g_geom *
447g_uncompress_taste(struct g_class *mp, struct g_provider *pp, int flags)
448{
449	struct cloop_header *header;
450	struct g_uncompress_softc *sc;
451	struct g_provider *pp2;
452	struct g_consumer *cp;
453	struct g_geom *gp;
454	uint32_t i, total_offsets, offsets_read, type;
455	uint8_t *buf;
456	int error;
457
458	g_trace(G_T_TOPOLOGY, "%s(%s,%s)", __func__, mp->name, pp->name);
459	g_topology_assert();
460
461	/* Skip providers that are already open for writing. */
462	if (pp->acw > 0)
463		return (NULL);
464
465	buf = NULL;
466
467	/*
468	 * Create geom instance.
469	 */
470	gp = g_new_geomf(mp, "%s.uncompress", pp->name);
471	cp = g_new_consumer(gp);
472	error = g_attach(cp, pp);
473	if (error == 0)
474		error = g_access(cp, 1, 0, 0);
475	if (error) {
476		g_detach(cp);
477		g_destroy_consumer(cp);
478		g_destroy_geom(gp);
479		return (NULL);
480	}
481	g_topology_unlock();
482
483	/*
484	 * Read cloop header, look for CLOOP magic, perform
485	 * other validity checks.
486	 */
487	DPRINTF(("%s: media sectorsize %u, mediasize %lld\n",
488	    gp->name, pp->sectorsize, pp->mediasize));
489
490	i = roundup(sizeof(struct cloop_header), pp->sectorsize);
491	buf = g_read_data(cp, 0, i, NULL);
492	if (buf == NULL)
493		goto err;
494
495	header = (struct cloop_header *) buf;
496	if (strncmp(header->magic, CLOOP_MAGIC_START,
497		    sizeof(CLOOP_MAGIC_START) - 1) != 0) {
498		DPRINTF(("%s: no CLOOP magic\n", gp->name));
499		goto err;
500	}
501
502	switch (header->magic[0x0b]) {
503	case 'L':
504		type = GEOM_ULZMA;
505		if (header->magic[0x0c] < GEOM_ULZMA_MAJVER) {
506			DPRINTF(("%s: image version too old\n", gp->name));
507			goto err;
508		}
509		printf("%s: GEOM_ULZMA image found\n", gp->name);
510		break;
511	case 'V':
512		type = GEOM_UZIP;
513		if (header->magic[0x0c] < GEOM_UZIP_MAJVER) {
514			DPRINTF(("%s: image version too old\n", gp->name));
515			goto err;
516		}
517		printf("%s: GEOM_UZIP image found\n", gp->name);
518		break;
519	default:
520		DPRINTF(("%s: unsupported image type\n", gp->name));
521		goto err;
522	}
523
524	DPRINTF(("%s: found CLOOP magic\n", gp->name));
525	/*
526	 * Initialize softc and read offsets.
527	 */
528	sc = malloc(sizeof(*sc), M_GEOM_UNCOMPRESS, M_WAITOK | M_ZERO);
529	gp->softc = sc;
530	sc->type = type;
531	sc->blksz = ntohl(header->blksz);
532	sc->nblocks = ntohl(header->nblocks);
533	if (sc->blksz % 4 != 0) {
534		printf("%s: block size (%u) should be multiple of 4.\n",
535		    gp->name, sc->blksz);
536		goto err;
537	}
538	if (sc->blksz > MAX_BLKSZ) {
539		printf("%s: block size (%u) should not be larger than %d.\n",
540		    gp->name, sc->blksz, MAX_BLKSZ);
541	}
542	total_offsets = sc->nblocks + 1;
543	if (sizeof(struct cloop_header) +
544	    total_offsets * sizeof(uint64_t) > pp->mediasize) {
545		printf("%s: media too small for %u blocks\n",
546		    gp->name, sc->nblocks);
547		goto err;
548	}
549	sc->offsets = malloc(
550	    total_offsets * sizeof(uint64_t), M_GEOM_UNCOMPRESS, M_WAITOK);
551	offsets_read = MIN(total_offsets,
552	    (pp->sectorsize - sizeof(*header)) / sizeof(uint64_t));
553	for (i = 0; i < offsets_read; i++)
554		sc->offsets[i] = be64toh(((uint64_t *) (header + 1))[i]);
555	DPRINTF(("%s: %u offsets in the first sector\n",
556	    gp->name, offsets_read));
557
558	free(buf, M_GEOM);
559	i = roundup((sizeof(struct cloop_header) +
560		total_offsets * sizeof(uint64_t)),pp->sectorsize);
561	buf = g_read_data(cp, 0, i, NULL);
562	if (buf == NULL)
563		goto err;
564	for (i = 0; i <= total_offsets; i++) {
565		sc->offsets[i] = be64toh(((uint64_t *)
566		    (buf+sizeof(struct cloop_header)))[i]);
567	}
568	DPRINTF(("%s: done reading offsets\n", gp->name));
569	mtx_init(&sc->last_mtx, "geom_uncompress cache", NULL, MTX_DEF);
570	sc->last_blk = -1;
571	sc->last_buf = malloc(sc->blksz, M_GEOM_UNCOMPRESS, M_WAITOK);
572	sc->req_total = 0;
573	sc->req_cached = 0;
574
575	switch (sc->type) {
576	case GEOM_ULZMA:
577		xz_crc32_init();
578		sc->s = xz_dec_init(XZ_SINGLE, 0);
579		sc->b = (struct xz_buf*)malloc(sizeof(struct xz_buf),
580		    M_GEOM_UNCOMPRESS, M_WAITOK);
581		break;
582	case GEOM_UZIP:
583		sc->zs = (z_stream *)malloc(sizeof(z_stream),
584		    M_GEOM_UNCOMPRESS, M_WAITOK);
585		sc->zs->zalloc = z_alloc;
586		sc->zs->zfree = z_free;
587		if (inflateInit(sc->zs) != Z_OK) {
588			goto err;
589		}
590		break;
591	}
592
593	g_topology_lock();
594	pp2 = g_new_providerf(gp, "%s", gp->name);
595	pp2->sectorsize = 512;
596	pp2->mediasize = (off_t)sc->nblocks * sc->blksz;
597	pp2->flags = pp->flags & G_PF_CANDELETE;
598	if (pp->stripesize > 0) {
599		pp2->stripesize = pp->stripesize;
600		pp2->stripeoffset = pp->stripeoffset;
601	}
602	g_error_provider(pp2, 0);
603	free(buf, M_GEOM);
604	g_access(cp, -1, 0, 0);
605
606	DPRINTF(("%s: taste ok (%d, %lld), (%d, %d), %x\n",
607	    gp->name,
608	    pp2->sectorsize, pp2->mediasize,
609	    pp2->stripeoffset, pp2->stripesize, pp2->flags));
610	printf("%s: %u x %u blocks\n",
611	    gp->name, sc->nblocks, sc->blksz);
612	return (gp);
613
614err:
615	g_topology_lock();
616	g_access(cp, -1, 0, 0);
617	if (buf != NULL)
618		free(buf, M_GEOM);
619	if (gp->softc != NULL) {
620		g_uncompress_softc_free(gp->softc, NULL);
621		gp->softc = NULL;
622	}
623	g_detach(cp);
624	g_destroy_consumer(cp);
625	g_destroy_geom(gp);
626	return (NULL);
627}
628
629static int
630g_uncompress_destroy_geom(struct gctl_req *req, struct g_class *mp,
631			  struct g_geom *gp)
632{
633	struct g_provider *pp;
634
635	g_trace(G_T_TOPOLOGY, "%s(%s, %s)", __func__, mp->name, gp->name);
636	g_topology_assert();
637
638	if (gp->softc == NULL) {
639		printf("%s(%s): gp->softc == NULL\n", __func__, gp->name);
640		return (ENXIO);
641	}
642
643	KASSERT(gp != NULL, ("NULL geom"));
644	pp = LIST_FIRST(&gp->provider);
645	KASSERT(pp != NULL, ("NULL provider"));
646	if (pp->acr > 0 || pp->acw > 0 || pp->ace > 0)
647		return (EBUSY);
648
649	g_uncompress_softc_free(gp->softc, gp);
650	gp->softc = NULL;
651	g_wither_geom(gp, ENXIO);
652	return (0);
653}
654
655static struct g_class g_uncompress_class = {
656	.name = UNCOMPRESS_CLASS_NAME,
657	.version = G_VERSION,
658	.taste = g_uncompress_taste,
659	.destroy_geom = g_uncompress_destroy_geom,
660
661	.start = g_uncompress_start,
662	.orphan = g_uncompress_orphan,
663	.access = g_uncompress_access,
664	.spoiled = g_uncompress_spoiled,
665};
666
667DECLARE_GEOM_CLASS(g_uncompress_class, g_uncompress);
668
669