/* Copyright (c)1994-2000 Begemot Computer Associates. All rights reserved.
 * See the file COPYRIGHT for details of redistribution and use. */

/*
 * This program makes a copy of a file and substitutes zero blocks
 * by holes. This can drastically reduce the storage requirements
 * for disc images.
 */
# include <stdio.h>
# include <stdlib.h>
# include <fcntl.h>
# include <unistd.h>
# include <string.h>
# include <errno.h>
# include <sys/types.h>
# include <sys/stat.h>
# ifdef HAVE_GETOPT_H
#  include <getopt.h>
# endif

# include <begemot.h>
# include "cdefs.h"
# include "util.h"

RCSID("$Id: zcp.c 476 2001-07-09 08:09:10Z hbb $")

u_long bufl = 0;

static char usgtxt[] =
"Usage: zcp [-v] [-b n] infile ofile\n"
"       zcp [-v] [-b n] file1 ... directory\n"
"Options:\n"
"	-v	be verbose (more v's may give more verbosity)\n"
"	-b n	set blocksize to n 512 byte blocks\n";

void copy(char *, char *);
void copy2dir(char *, char *);
int isdir(char *);

int
main(int argc, char *argv[])
{
	int opt, i;

	set_argv0(argv[0]);
	while((opt = getopt(argc, argv, "b:v")) != EOF)
		switch(opt) {

		  case 'b':
			bufl = strtoul(optarg, NULL, 0);
			break;

		  case 'v':
			verb_option("all");
			break;
		}

	argc -= optind;
	argv += optind;

	if(bufl == 0)
		bufl = 1;
	bufl *= 512;

	if(argc < 2) {
		fprintf(stderr, usgtxt);
		return 1;
	}

	if(isdir(argv[argc-1])) {
		for(i = 0; i < argc - 1; i++)
			copy2dir(argv[i], argv[argc-1]);
	} else {
		if(argc != 2) {
			fprintf(stderr, usgtxt);
			return 1;
		}
		copy(argv[0], argv[1]);
	}

	return 0;
}

void
copy(char *from, char *to)
{
	int ifd, ofd, ret, oret;
	u_char *buf, *p;
	off_t pos, opos;

	if((ifd = open(from, O_RDONLY, 0)) < 0)
		panic("%s: %s", from, strerror(errno));
	if((ofd = open(to, O_WRONLY|O_CREAT|O_TRUNC, 0666)) < 0)
		panic("%s: %s", to, strerror(errno));

	verb(V_DFLT, 1, "copy from %s to %s; blocksize %lu bytes", from, to, bufl);

	buf = xalloc(bufl);

	opos = pos = 0;
	while((ret = read(ifd, buf, bufl)) > 0) {
		for(p = buf; p < &buf[ret]; p++)
			if(*p != 0)
				break;
		if(p == &buf[ret]) {
			pos += ret;
			verb(V_DFLT, 2, "skipping %d bytes", ret);
			continue;
		}
		if(opos != pos) {
			verb(V_DFLT, 2, "seeking to %qd", (long long)pos);
			if(lseek(ofd, pos, SEEK_SET) != pos)
				panic("seek: %s", strerror(errno));
			opos = pos;
		}
		verb(V_DFLT, 2, "writing %d bytes", ret);
		if((oret = write(ofd, buf, ret)) != ret) {
			if(oret < 0)
				panic("write: %s", strerror(errno));
			panic("short write: %d instead of %d", oret, ret);
		}
		pos += ret;
		opos += ret;
	}
	if(ret < 0)
		panic("read: %s", strerror(errno));

	if(opos != pos) {
		verb(V_DFLT, 1, "extending to %qd", (long long)pos);
		if(ftruncate(ofd, pos))
			panic("ftruncate: %s", strerror(errno));
		opos = pos;
	}

	verb(V_DFLT, 1, "done.");

	free(buf);
}

void
copy2dir(char *from, char *dir)
{
	char *fname;
	char *base;

	if((base = strrchr(from, '/')) == NULL)
		base = from;
	else
		base++;

	fname = xalloc(strlen(base) + strlen(dir) + 2);
	sprintf(fname, "%s/%s", dir, base);
	copy(from, fname);
	free(fname);
}

int
isdir(char *name)
{
	struct stat statb;

	if(stat(name, &statb))
		return 0;
	if((statb.st_mode & S_IFMT) == S_IFDIR)
		return 1;
	return 0;
}
