/*
** Combined mv/cp/ln command:
**	mv file1 file2
**	mv dir1 dir2
**	mv file1 ... filen dir1
*/

char xxxsccsid[] = "@(#)mv.c	2.1	";



#include	<stdio.h>
#include	<sys/types.h>
#include	<sys/stat.h>
#include	<errno.h>

#define EQ(x,y)	!strcmp(x,y)
#define	DOT	"."
#define	DOTDOT	".."
#define	DELIM	'/'
#define	MAXN	128
#define MODEBITS 07777
#if u370
#define BLKSIZE   4096
#else
#define BLKSIZE    2048
#endif

char	*pname();
char	*dname();
char	*strrchr();
extern	errno;

struct	stat s1, s2;

char	*cmd;
int	f_flag = 0;

main(argc, argv)
register char *argv[];
{
	register i, r;

	if (cmd = strrchr(argv[0], '/'))
		++cmd;
	else
		cmd = argv[0];
	if (!EQ(cmd, "cp") && !EQ(cmd, "mv") && !EQ(cmd, "ln")) {
		fprintf(stderr, "%s: command must be named cp|mv|ln--defaults to `cp'\n", cmd);
		cmd = "cp";
	}

	if (EQ(argv[1], "-f"))
		++f_flag, --argc, ++argv;

	if (argc < 3)
		usage();
	stat(argv[1], &s1);
	if (((s1.st_mode & S_IFMT) == S_IFDIR) && EQ(cmd, "mv")) {
		if (argc != 3)
			usage();
		exit(mvdir(argv[1], argv[2]));
	}
	setuid(getuid());
	if (argc > 3)
		if (stat(argv[argc-1], &s2) < 0) {
			fprintf(stderr, "%s: %s not found\n", cmd, argv[argc-1]);
			exit(2);
		} else if ((s2.st_mode & S_IFMT) != S_IFDIR)
			usage();
	r = 0;
	for (i=1; i<argc-1; i++)
		r += move(argv[i], argv[argc-1]);
	exit(r?2:0);
}

move(source, target)
char *source, *target;
{
	register c, i;
	char	buf[MAXN];

	if (stat(source, &s1) < 0) {
		fprintf(stderr, "%s: cannot access %s\n", cmd, source);
		return(1);
	}
	if ((s1.st_mode & S_IFMT) == S_IFDIR) {
		fprintf(stderr, "%s : <%s> directory\n", cmd, source);
		return(1);
	}
	if (EQ(cmd, "mv") && access(pname(source), 2) == -1)
		goto s_unlink;
	s2.st_mode = S_IFREG;
	if (stat(target, &s2) >= 0) {
		if ((s2.st_mode & S_IFMT) == S_IFDIR) {
			sprintf(buf, "%s/%s", target, dname(source));
			target = buf;
		}
		if (stat(target, &s2) >= 0) {
			if (s1.st_dev==s2.st_dev && s1.st_ino==s2.st_ino) {
				fprintf(stderr, "%s: %s and %s are identical\n", cmd,
						source, target);
				return(1);
			}
			if(EQ(cmd, "cp"))
				goto skip;
			if (access(target, 2) < 0 && isatty(fileno(stdin))
			&& !f_flag) {
				fprintf(stderr, "%s: %s: %o mode ", cmd, target,
					s2.st_mode & MODEBITS);
				i = c = getchar();
				while (c != '\n' && c != EOF)
					c = getchar();
				if (i != 'y')
					return(1);
			}
			if (unlink(target) < 0) {
				fprintf(stderr, "%s: cannot unlink %s\n", cmd, target);
				return(1);
			}
		}
	}
skip:
	if (EQ(cmd, "cp") || link(source, target) < 0) {
		int from, to, ct, oflg;
		char fbuf[BLKSIZE];

		if (EQ(cmd, "ln")) {
			if(errno == EXDEV)
				fprintf(stderr, "%s: different file system\n", cmd);
			else
				fprintf(stderr, "%s: no permission for %s\n", cmd, target);
			return(1);
		}
		if((from = open(source, 0)) < 0) {
			fprintf(stderr, "%s: cannot open %s\n", cmd, source);
			return 1;
		}
		oflg = access(target, 0) == -1;
		if((to = creat (target, 0666)) < 0) {
			fprintf(stderr, "%s: cannot create %s\n", cmd, target);
			return 1;
		}
		while((ct = read(from, fbuf, BLKSIZE)) > 0)
			if(ct < 0 || write(to, fbuf, ct) != ct) {
				fprintf(stderr, "%s: bad copy to %s\n", cmd, target);
				if((s2.st_mode & S_IFMT) == S_IFREG)
					unlink(target);
				return 1;
			}
		close(from), close(to);
		if (oflg)
			chmod(target, s1.st_mode);
	}
	if (!EQ(cmd, "mv"))
		return 0;
	if (unlink(source) < 0) {
s_unlink:
		fprintf(stderr, "%s: cannot unlink %s\n", cmd, source);
		return(1);
	}
	return(0);
}

mvdir(source, target)
char *source, *target;
{
	register char *p;

	if (stat(target, &s2) >= 0) {
		fprintf(stderr, "%s: directory %s exists\n", cmd, target);
		return(2);
	}
	if (EQ(source, target)) {
		fprintf(stderr, "%s: ?? source == target, source exists and target doesnt\n", cmd);
		return(2);
	}
	p = dname(source);
	if (EQ(p, DOT) || EQ(p, DOTDOT) || !*p || p[strlen(p)-1]=='/') {
		fprintf(stderr, "%s: cannot rename %s\n", cmd, p);
		return(2);
	}
	p = pname(source);
	if (stat(p, &s1) < 0 || stat(pname(target), &s2) < 0) {
		fprintf(stderr, "%s: cannot locate parent\n", cmd);
		return(2);
	}
	if (s1.st_dev != s2.st_dev || s1.st_ino != s2.st_ino) {
		fprintf(stderr, "%s: directory rename only\n", cmd);
		return(2);
	}
	if (access(p, 2) < 0) {
		fprintf(stderr, "%s: no write access to %s\n", cmd, p);
		return(2);
	}
	if (link(source, target) < 0) {
		fprintf(stderr, "%s: cannot link %s and %s\n", cmd,
			source, target);
		return(2);
	}
	if (unlink(source) < 0) {
		fprintf(stderr, "%s: ?? cannot unlink %s\n", cmd, source);
		return(2);
	}
	return(0);
}

char *
pname(name)
register char *name;
{
	register c;
	register char *p, *q;
	static	char buf[MAXN];

	p = q = buf;
	while (c = *p++ = *name++)
		if (c == DELIM)
			q = p-1;
	if (q == buf && *q == DELIM)
		q++;
	*q = 0;
	return buf[0]? buf : DOT;
}

char *
dname(name)
register char *name;
{
	register char *p;

	p = name;
	while (*p)
		if (*p++ == DELIM && *p)
			name = p;
	return name;
}

usage()
{
	fprintf(stderr, "Usage: {mv|cp|ln} f1 f2\n%s\n%s\n",
		"       {mv|cp|ln} f1 ... fn d1", "       mv d1 d2");
	exit(2);
}
