/*
 * Copyright (c) 1983 Regents of the University of California.
 * All rights reserved.  The Berkeley software License Agreement
 * specifies the terms and conditions for redistribution.
 */

#ifndef lint
char copyright[] =
"@(#) Copyright (c) 1983 Regents of the University of California.\n\
 All rights reserved.\n";
#endif not lint

#ifndef lint
static	char sccsid[] = "@(#)cp.c 1.1 90/03/23 SMI"; /* from UCB 4.13 10/11/85 */
#endif not lint

/*
 * cp
 */
#include <stdio.h>
#include <sys/param.h>
#include <sys/stat.h>
#include <sys/dir.h>
#include <sys/time.h>
#include <sys/mman.h>

#ifndef MAXMAPSIZE
#define	MAXMAPSIZE	(256*1024)
#endif

int	iflag;
int	rflag;
int	pflag;
int	zflag;
char	*rindex();
caddr_t	mmap();

main(argc, argv)
	int argc;
	char **argv;
{
	struct stat stb;
	int rc, i;

	argc--, argv++;
	while (argc > 0 && **argv == '-') {
		(*argv)++;
		while (**argv) switch (*(*argv)++) {

		case 'i':
			iflag++; break;

		case 'R':
		case 'r':
			rflag++; break;

		case 'p':	/* preserve mtimes, atimes, and modes */
			pflag++;
			(void) umask(0);
			break;

		default:
			goto usage;
		}
		argc--; argv++;
	}
	if (argc < 2) 
		goto usage;
	if (argc > 2) {
		if (stat(argv[argc-1], &stb) < 0)
			goto usage;
		if ((stb.st_mode&S_IFMT) != S_IFDIR) 
			goto usage;
	}
	rc = 0;
	for (i = 0; i < argc-1; i++)
		rc |= copy(argv[i], argv[argc-1]);
	exit(rc);
usage:
	(void) fprintf(stderr,
	    "Usage: cp [-ip] f1 f2; or: cp [-ipr] f1 ... fn d2\n");
	exit(1);
	/* NOTREACHED */
}

copy(from, to)
	char *from, *to;
{
	int fold, fnew, n, exists;
	char *last, destname[MAXPATHLEN + 1], buf[MAXBSIZE];
	struct stat stfrom, stto;
	register caddr_t cp;
	int mapsize, munmapsize;
	register off_t filesize;
	register off_t offset;

	fold = open(from, 0);
	if (fold < 0) {
		Perror(from);
		return (1);
	}
	if (fstat(fold, &stfrom) < 0) {
		Perror(from);
		(void) close(fold);
		return (1);
	}
	if (stat(to, &stto) >= 0 &&
	   (stto.st_mode&S_IFMT) == S_IFDIR) {
		last = rindex(from, '/');
		if (last) last++; else last = from;
		if (strlen(to) + strlen(last) >= sizeof destname - 1) {
			(void) fprintf(stderr, "cp: %s/%s: Name too long\n",
			    to, last);
			(void) close(fold);
			return(1);
		}
		(void) sprintf(destname, "%s/%s", to, last);
		to = destname;
	}
	if (rflag && (stfrom.st_mode&S_IFMT) == S_IFDIR) {
		int fixmode = 0;	/* cleanup mode after rcopy */

		(void) close(fold);
		if (stat(to, &stto) < 0) {
			if (mkdir(to,
			    ((int)stfrom.st_mode & 07777) | 0700) < 0) {
				Perror(to);
				return (1);
			}
			fixmode = 1;
		} else if ((stto.st_mode&S_IFMT) != S_IFDIR) {
			(void) fprintf(stderr, "cp: %s: Not a directory.\n",
			    to);
			return (1);
		} else if (pflag)
			fixmode = 1;
		n = rcopy(from, to);
		if (fixmode)
			(void) chmod(to, (int)stfrom.st_mode & 07777);
		return (n);
	}

	if ((stfrom.st_mode&S_IFMT) == S_IFDIR) {
		(void) close(fold);
		(void) fprintf(stderr, "cp: %s: Is a directory (not copied).\n",
		    from);
		return (1);
	}

	exists = stat(to, &stto) == 0;
	if (exists) {
		if (stfrom.st_dev == stto.st_dev &&
		   stfrom.st_ino == stto.st_ino) {
			(void) fprintf(stderr,
				"cp: %s and %s are identical (not copied).\n",
					from, to);
			(void) close(fold);
			return (1);
		}
		if (iflag && isatty(fileno(stdin))) {
			int i, c;

			(void) fprintf (stderr, "overwrite %s? ", to);
			i = c = getchar();
			while (c != '\n' && c != EOF)
				c = getchar();
			if (i != 'y') {
				(void) close(fold);
				return(1);
			}
		}
	}
	fnew = creat(to, (int)stfrom.st_mode & 07777);
	if (fnew < 0) {
		Perror(to);
		(void) close(fold); return(1);
	}
	if (exists && pflag)
		(void) fchmod(fnew, (int)stfrom.st_mode & 07777);

	zopen(fnew, zflag);

	if ((stfrom.st_mode & S_IFMT) == S_IFREG) {
		/*
		 * Determine size of initial mapping.  This will determine
		 * the size of the address space chunk we work with.  This
		 * initial mapping size will be used to perform munmap() in
		 * the future.
		 */
		mapsize = MAXMAPSIZE;
		if (stfrom.st_size < mapsize)
			mapsize = stfrom.st_size;
		munmapsize = mapsize;

		/*
		 * Mmap time!
		 */
		cp = mmap((caddr_t)NULL, mapsize, PROT_READ, MAP_SHARED, fold,
		    (off_t)0);
		if (cp == (caddr_t)-1)
			mapsize = 0;	/* I guess we can't mmap today */
	} else
		mapsize = 0;		/* can't mmap non-regular files */

	if (mapsize != 0) {
		offset = 0;
		filesize = stfrom.st_size;
#ifdef MC_ADVISE
		(void) madvise(cp, mapsize, MADV_SEQUENTIAL);
#endif
		for (;;) {
			if (zwrite(fnew, cp, mapsize) < 0) {
				Perror(to);
				(void) close(fold);
				(void) close(fnew);
				(void) munmap(cp, munmapsize);
				return (1);
			}
			filesize -= mapsize;
			if (filesize == 0)
				break;
			offset += mapsize;
			if (filesize < mapsize)
				mapsize = filesize;
			if (mmap(cp, mapsize, PROT_READ, MAP_SHARED | MAP_FIXED,
			    fold, offset) == (caddr_t)-1) {
				Perror(from);
				(void) close(fold);
				(void) close(fnew);
				(void) munmap(cp, munmapsize);
				return (1);
			}
#ifdef MC_ADVISE
			(void) madvise(cp, mapsize, MADV_SEQUENTIAL);
#endif
		}
		(void) munmap(cp, munmapsize);
	} else {
		for (;;) {
			n = read(fold, buf, sizeof buf);
			if (n == 0)
				break;
			if (n < 0) {
				Perror(from);
				(void) close(fold);
				(void) close(fnew);
				return (1);
			}
			if (zwrite(fnew, buf, n) < 0) {
				Perror(to);
				(void) close(fold);
				(void) close(fnew);
				return (1);
			}
		}
	}
	(void) close(fold);
	if (zclose(fnew) < 0) {
		Perror(to);
		(void) close(fnew);
		return (1);
	}
	if (close(fnew) < 0) {
		Perror(to);
		return (1);
	}
	if (pflag)
		return (setimes(to, &stfrom));
	return (0);
}

rcopy(from, to)
	char *from, *to;
{
	DIR *fold = opendir(from);
	struct direct *dp;
	struct stat statb;
	int errs = 0;
	char fromname[MAXPATHLEN + 1];

	if (fold == 0 || (pflag && fstat(fold->dd_fd, &statb) < 0)) {
		Perror(from);
		return (1);
	}
	for (;;) {
		dp = readdir(fold);
		if (dp == 0) {
			(void) closedir(fold);
			if (pflag)
				return (setimes(to, &statb) + errs);
			return (errs);
		}
		if (dp->d_ino == 0)
			continue;
		if (!strcmp(dp->d_name, ".") || !strcmp(dp->d_name, ".."))
			continue;
		if (strlen(from)+1+strlen(dp->d_name) >= sizeof fromname - 1) {
			(void) fprintf(stderr, "cp: %s/%s: Name too long\n",
			    from, dp->d_name);
			errs++;
			continue;
		}
		(void) sprintf(fromname, "%s/%s", from, dp->d_name);
		errs += copy(fromname, to);
	}
}

int
setimes(path, statp)
	char *path;
	struct stat *statp;
{
	struct timeval tv[2];
	
	tv[0].tv_sec = statp->st_atime;
	tv[1].tv_sec = statp->st_mtime;
	tv[0].tv_usec = tv[1].tv_usec = 0;
	if (utimes(path, tv) < 0) {
		Perror(path);
		return (1);
	}
	return (0);
}

Perror(s)
	char *s;
{

	(void) fprintf(stderr, "cp: ");
	perror(s);
}


/*
 * sparse file support
 */

#include <errno.h>
#include <sys/file.h>

static int zbsize;
static int zlastseek;
off_t lseek();

/* is it ok to try to create holes? */
zopen(fd, flag)
	int fd;
{
	struct stat st;

	zbsize = 0;
	zlastseek = 0;

	if (flag &&
		fstat(fd, &st) == 0 &&
		(st.st_mode & S_IFMT) == S_IFREG)
		zbsize = st.st_blksize;
}

/* write and/or seek */
zwrite(fd, buf, nbytes)
	int fd;
	register char *buf;
	register int nbytes;
{
	register int block = zbsize ? zbsize : nbytes;

	do {
		if (block > nbytes)
			block = nbytes;
		nbytes -= block;

		if (!zbsize || notzero(buf, block)) {
			register int n, count = block;

			do {
				if ((n = write(fd, buf, count)) < 0)
					return -1;
				buf += n;
			} while ((count -= n) > 0);
			zlastseek = 0;
		}
		else {
			if (lseek(fd, (off_t) block, L_INCR) < 0)
				return -1;
			buf += block;
			zlastseek = 1;
		}
	} while (nbytes > 0);

	return 0;
}

/* write last byte of file if necessary */
zclose(fd)
	int fd;
{
	zbsize = 0;

	if (zlastseek &&
		(lseek(fd, (off_t) -1, L_INCR) < 0 ||
		zwrite("", 1) < 0))
		return -1;
 	else
		return 0;
}

/* return true if buffer is not all zeros */
notzero(p, n)
	register char *p;
	register int n;
{
	register int result = 0;

	while ((int) p & 3 && --n >= 0)
		result |= *p++;

	while ((n -= 4 * sizeof (int)) >= 0) {
		result |= ((int *) p)[0];
		result |= ((int *) p)[1];
		result |= ((int *) p)[2];
		result |= ((int *) p)[3];
		if (result)
			return result;
		p += 4 * sizeof (int);
	}
	n += 4 * sizeof (int);

	while (--n >= 0)
		result |= *p++;

	return result;	
}
