/*
 * CDDL HEADER START
 *
 * The contents of this file are subject to the terms of the
 * Common Development and Distribution License (the "License").
 * You may not use this file except in compliance with the License.
 *
 * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
 * or http://www.opensolaris.org/os/licensing.
 * See the License for the specific language governing permissions
 * and limitations under the License.
 *
 * When distributing Covered Code, include this CDDL HEADER in each
 * file and include the License file at usr/src/OPENSOLARIS.LICENSE.
 * If applicable, add the following below this CDDL HEADER, with the
 * fields enclosed by brackets "[]" replaced with your own identifying
 * information: Portions Copyright [yyyy] [name of copyright owner]
 *
 * CDDL HEADER END
 */
/*
 * Copyright 2007 Sun Microsystems, Inc.  All rights reserved.
 * Use is subject to license terms.
 */

#pragma ident	"%Z%%M%	%I%	%E% SMI"

/*
 * fiocompress - a utility to compress files with a filesystem.
 * Used to build compressed boot archives to reduce memory
 * requirements for booting.
 */

#include <stdio.h>
#include <errno.h>
#include <stdlib.h>
#include <fcntl.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <sys/mman.h>
#include <unistd.h>
#include <utility.h>
#include <zlib.h>

#include <sys/filio.h>
#include <sys/fs/decomp.h>

#include "message.h"

static void	setup_infile(char *);
static void	setup_outfile(char *);
static void	do_comp(size_t);
static void	do_decomp(void);

static caddr_t	srcaddr;
static size_t	srclen;

static int	dstfd;

static char	*srcfile;
static char	*dstfile;


int
main(int argc, char **argv)
{
	int compress = 0;
	int decompress = 0;
	int doioc = 0;
	size_t	blksize = 8192;
	char c;

	while ((c = getopt(argc, argv, "mcdb:")) != -1) {
		switch (c) {
		case 'm':
			doioc++;
			break;
		case 'c':
			if (decompress) {
				(void) fprintf(stderr, OPT_DC_EXCL);
				exit(-1);
			}
			compress = 1;
			break;
		case 'd':
			if (compress) {
				(void) fprintf(stderr, OPT_DC_EXCL);
				exit(-1);
			}
			decompress = 1;
			break;
		case 'b':
			blksize = atoi(optarg);
			if (blksize == 0 || (blksize & (blksize-1))) {
				(void) fprintf(stderr, INVALID_BLKSZ);
				exit(-1);
			}
			break;
		case '?':
			(void) fprintf(stderr, UNKNOWN_OPTION, optopt);
			exit(-1);
		}
	}
	if (argc - optind != 2) {
		(void) fprintf(stderr, MISS_FILES);
		exit(-1);
	}

	setup_infile(argv[optind]);
	setup_outfile(argv[optind + 1]);

	if (decompress)
		do_decomp();
	else {
		do_comp(blksize);
		if (doioc) {
			if (ioctl(dstfd, _FIO_COMPRESSED, 0) == -1) {
				(void) fprintf(stderr, FIO_COMP_FAIL,
				    dstfile, strerror(errno));
				exit(-1);
			}
		}
	}
	return (0);
}

static void
setup_infile(char *file)
{
	int fd;
	void *addr;
	struct stat stbuf;

	srcfile = file;

	fd = open(srcfile, O_RDONLY, 0);
	if (fd == -1) {
		(void) fprintf(stderr, CANT_OPEN,
		    srcfile, strerror(errno));
		exit(-1);
	}

	if (fstat(fd, &stbuf) == -1) {
		(void) fprintf(stderr, STAT_FAIL,
		    srcfile, strerror(errno));
		exit(-1);
	}
	srclen = stbuf.st_size;

	addr = mmap(0, srclen, PROT_READ, MAP_SHARED, fd, 0);
	if (addr == MAP_FAILED) {
		(void) fprintf(stderr, MMAP_FAIL, srcfile, strerror(errno));
		exit(-1);
	}
	srcaddr = addr;
}

static void
setup_outfile(char *file)
{
	int fd;

	dstfile = file;

	fd = open(dstfile, O_WRONLY | O_CREAT | O_TRUNC,
	    S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH);
	if (fd == -1) {
		(void) fprintf(stderr, OPEN_FAIL, dstfile, strerror(errno));
		exit(-1);
	}
	dstfd = fd;
}

static void
do_comp(size_t blksize)
{
	struct comphdr *hdr;
	off_t offset;
	size_t blks, dstlen, hlen;
	void *dstbuf;
	int i;

	blks = ((srclen - 1) / blksize) + 1;
	hlen = offset = sizeof (struct comphdr) + blks * sizeof (uint64_t);
	hdr = malloc(hlen);
	if (hdr == NULL) {
		(void) fprintf(stderr, HDR_ALLOC, hlen);
		exit(-1);
	}

	hdr->ch_magic = CH_MAGIC;
	hdr->ch_version = CH_VERSION;
	hdr->ch_algorithm = CH_ALG_ZLIB;
	hdr->ch_fsize = srclen;
	hdr->ch_blksize = blksize;

	dstlen = ZMAXBUF(blksize);
	dstbuf = malloc(dstlen);
	if (dstbuf == NULL) {
		(void) fprintf(stderr, BUF_ALLOC, dstlen);
		exit(-1);
	}

	if (lseek(dstfd, offset, SEEK_SET) == (off_t)-1) {
		(void) fprintf(stderr, SEEK_ERR,
		    offset, dstfile, strerror(errno));
		exit(-1);
	}

	for (i = 0; i < blks; i++) {
		ulong_t slen, dlen;
		int ret;

		hdr->ch_blkmap[i] = offset;
		slen = MIN(srclen, blksize);
		dlen = dstlen;
		ret = compress2(dstbuf, &dlen, (Bytef *)srcaddr, slen, 9);
		if (ret != Z_OK) {
			(void) fprintf(stderr, COMP_ERR, srcfile, ret);
			exit(-1);
		}

		if (write(dstfd, dstbuf, dlen) != dlen) {
			(void) fprintf(stderr, WRITE_ERR,
			    dlen, dstfile, strerror(errno));
			exit(-1);
		}

		offset += dlen;
		srclen -= slen;
		srcaddr += slen;
	}

	if (lseek(dstfd, 0, SEEK_SET) == (off_t)-1) {
		(void) fprintf(stderr, SEEK_ERR,
		    0, dstfile, strerror(errno));
		exit(-1);
	}

	if (write(dstfd, hdr, hlen) != hlen) {
		(void) fprintf(stderr, WRITE_ERR,
		    hlen, dstfile, strerror(errno));
		exit(-1);
	}
}

static void
do_decomp()
{
	struct comphdr *hdr;
	size_t blks, blksize;
	void *dstbuf;
	int i;
	ulong_t slen, dlen;
	int ret;

	hdr = (struct comphdr *)(void *)srcaddr;
	if (hdr->ch_magic != CH_MAGIC) {
		(void) fprintf(stderr, BAD_MAGIC,
		    srcfile, (uint64_t)hdr->ch_magic, CH_MAGIC);
		exit(-1);
	}
	if (hdr->ch_version != CH_VERSION) {
		(void) fprintf(stderr, BAD_VERS,
		    srcfile, (uint64_t)hdr->ch_version, CH_VERSION);
		exit(-1);
	}
	if (hdr->ch_algorithm != CH_ALG_ZLIB) {
		(void) fprintf(stderr, BAD_ALG,
		    srcfile, (uint64_t)hdr->ch_algorithm, CH_ALG_ZLIB);
		exit(-1);
	}

	blksize = hdr->ch_blksize;
	dstbuf = malloc(blksize);
	if (dstbuf == NULL) {
		(void) fprintf(stderr, HDR_ALLOC, blksize);
		exit(-1);
	}

	blks = (hdr->ch_fsize - 1) / blksize;
	srcaddr += hdr->ch_blkmap[0];
	for (i = 0; i < blks; i++) {
		dlen = blksize;
		slen = hdr->ch_blkmap[i + 1] - hdr->ch_blkmap[i];
		ret = uncompress(dstbuf, &dlen, (Bytef *)srcaddr, slen);
		if (ret != Z_OK) {
			(void) fprintf(stderr, DECOMP_ERR, srcfile, ret);
			exit(-1);
		}

		if (dlen != blksize) {
			(void) fprintf(stderr, CORRUPT, srcfile);
			exit(-1);
		}
		if (write(dstfd, dstbuf, dlen) != dlen) {
			(void) fprintf(stderr, WRITE_ERR,
			    dlen, dstfile, strerror(errno));
			exit(-1);
		}
		srcaddr += slen;
	}

	dlen = blksize;
	slen = hdr->ch_fsize - hdr->ch_blkmap[i];
	if ((ret = uncompress(dstbuf, &dlen, (Bytef *)srcaddr, slen)) != Z_OK) {
		(void) fprintf(stderr, DECOMP_ERR, dstfile, ret);
		exit(-1);
	}

	if (write(dstfd, dstbuf, dlen) != dlen) {
		(void) fprintf(stderr, WRITE_ERR,
		    dlen, dstfile, strerror(errno));
		exit(-1);
	}
}