/*	Copyright (c) 1984 AT&T	*/
/*	  All Rights Reserved  	*/

/*	THIS IS UNPUBLISHED PROPRIETARY SOURCE CODE OF AT&T	*/
/*	The copyright notice above does not evidence any   	*/
/*	actual or intended publication of such source code.	*/

#ident	"$Header: mv.c 1.17 90/05/01 $@(#)mv:mv.c	1.26"

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

/*
 * $Copyright:	$
 * Copyright (c) 1984, 1985, 1986, 1987, 1988, 1989, 1990 
 * Sequent Computer Systems, Inc.   All rights reserved.
 *  
 * This software is furnished under a license and may be used
 * only in accordance with the terms of that license and with the
 * inclusion of the above copyright notice.   This software may not
 * be provided or otherwise made available to, or used by, any
 * other person.  No title to or ownership of the software is
 * hereby transferred.
 *
 */

/*
 * This modified to use the rename() system call when moving a directory
 * instead of exec'ing /usr/lib/mv_dir.
 */


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

#define MODEBITS	07777
#define FTYPE(A)	(A.st_mode)
#define FMODE(A)	(A.st_mode)
#define	IDENTICAL(A,B)	(A.st_dev==B.st_dev && A.st_ino==B.st_ino)
#define ISBLK(A)	((A.st_mode & S_IFMT) == S_IFBLK)
#define ISCHR(A)	((A.st_mode & S_IFMT) == S_IFCHR)
#define ISDIR(A)	((A.st_mode & S_IFMT) == S_IFDIR)
#define ISFIFO(A)	((A.st_mode & S_IFMT) == S_IFIFO)
#define ISREG(A)	((A.st_mode & S_IFMT) == S_IFREG)
#define ISLNK(A)	((A.st_mode & S_IFMT) == S_IFLNK)

#define BLKSIZE	8192
#define	DOT	"."
#define DOTDOT	".."
#define	DELIM	'/'
#define EQ(x,y)	!strcmp(x,y)
#define	FALSE	0
#define MODEBITS 07777
#define TRUE 1

extern void perror();
extern int errno;

char	*malloc();
char	*valloc();
char	*dname();
char	*strrchr();
int	utime();
extern	int errno;
extern  char *optarg;
extern	int optind, opterr;
struct stat s1, s2;
int cpy = FALSE;	
int mve = FALSE;	
int lnk = FALSE;	
char	*cmd;
int	silent = 0;
int	symflag = 0;
int	(*statf)();

extern int stat(), lstat();
extern int link(), symlink();

main(argc, argv)
register char *argv[];
{
	register int c, i, r, errflg = 0;
	
	/*
	 * Determine command invoked (mv, cp, or ln) 
	 */
	
	if (cmd = strrchr(argv[0], '/'))
		++cmd;
	else
		cmd = argv[0];
	
	/*
	 * Set flags based on command.
	 */
	 
	  if (EQ(cmd, "mv"))
	  	mve = TRUE;
	  else if (EQ(cmd, "ln")) 
		   lnk = TRUE;
	  	else
		   cpy = TRUE;   /* default */
	
	/*
	 * Check for options:
	 * 	cp file1 [file2 ...] target
	 *	ln [-f] [-s] file1 [file2 ...] target
	 *	mv [-f] file1 [file2 ...] target
	 *	mv [-f] dir1 target
	 */
	 
	if (cpy) {
		while ((c = getopt(argc, argv,"")) != EOF) 
			errflg++;
	} else if (lnk) {

		while ((c = getopt(argc, argv,"fs")) != EOF) 
	 	switch(c) {
			case 's':
				symflag++;
				break;
			case 'f':
				silent++;
				break;
			default:
				errflg++;
		}
	} else {

		while ((c = getopt(argc, argv,"f")) != EOF) 
	 	switch(c) {
			case 'f':
				silent++;
				break;
			default:
				errflg++;
		}
	}
	
	/*
	 * Check for sufficient arguments 
	 * or a usage error.
	 */

	argc -= optind;	 
	argv  = &argv[optind];
	
	if (argc < 2) {
		fprintf(stderr,
		    gettxt("mv:1", "%s: Insufficient arguments (%d)\n"),
		    cmd,argc);
		usage();
	}
	
	if (errflg != 0)
		usage();
	
	/* Determine which stat function based on command name. */

	if (mve)
		statf = lstat;
	else
		statf = stat;

	/*
	 * If a move command, and more than one argument, then last
	 * argument (target) must exist and must be a directory.
	 * Target can also be a symlink pointing to a directory
	 * which exists, so use stat instead of lstat.
	 */

	if (mve && argc > 2) {
		if (stat(argv[argc-1], &s1) != 0) {
		    fprintf(stderr, gettxt("mv:3", "%s: %s not found\n"),
			cmd, argv[argc-1]);
		    exit(2);
		}
		if (!ISDIR(s1)) {
		    fprintf(stderr,
		        gettxt("mv:4", "%s: Target must be directory\n"), cmd);
		    usage();
		}
	}
	
	/*
	 * If there is more than a source and target,
	 * the last argument (the target) must be a directory
	 * which really exists.
	 *
	 * Already tested args for mv command above.
	 */
	 
	if ( !mve && (symflag == 0) && (argc > 2)) {
		if (statf(argv[argc-1], &s2) < 0) {
			fprintf(stderr,
				gettxt("mv:3", "%s: %s not found\n"),
				cmd, argv[argc-1]);
			exit(2);
		}
		
		if (!ISDIR(s2)) {
			fprintf(stderr,
			    gettxt("mv:4", "%s: Target must be directory\n"),
			    cmd);
			usage();
		}
	}	
	
	/*
	 * Perform a multiple argument mv|cp|ln by
	 * multiple invocations of move().
	 */
	 
	r = 0;
	for (i=0; i<argc-1; i++)
		r += move(argv[i], argv[argc-1]);
	
	/* 
	 * Show errors by nonzero exit code.
	 */
	 
	 exit(r?2:0);
}

move(source, target)
char *source, *target;
{
	register last, c, i;
	char	*buf = (char *)NULL;
	int from, to, ct, oflg;
	static char *fbuf = (char *)NULL;
	struct stat s3;
	char *lc;			/* last component of source */
	char dest[MAXPATHLEN + 2];	/* destination if target is a direcory */
	
	struct	utimbuf	{
		time_t	actime;
		time_t	modtime;
		};
	struct utimbuf *times;

	if (fbuf == NULL) {
		fbuf = valloc(BLKSIZE);
		if (fbuf == NULL) {
			fprintf(stderr, "%s: Cannot valloc fbuf.\n", cmd);
			exit(1);
		}
	}

	/* 
	 * While source or target have trailing 
	 * DELIM (/), remove them (unless only "/")
	 */

	while (((last = strlen(source)) > 1)
	    &&  (source[last-1] == DELIM))
		 source[last-1]=NULL;
	
	while (((last = strlen(target)) > 1)
	    &&  (target[last-1] == DELIM))
		 target[last-1]=NULL;
		
	/*
	 * Do the symlink if requested.
	 * Will allow creation of symlink under a directory.
	 */

	if (symflag) {
		if ((lstat(target, &s1) == 0) && ISDIR(s1)) {
			lc = strrchr(source, '/');
			if (lc == (char *) NULL)
				lc = source;
			else {
				lc++;
				if (lc == '\0')
					lc = source;
			}
			if ((strlen(source) + strlen(target) + 1) > MAXPATHLEN) {
				fprintf(stderr, 
					gettxt("mv:37", "%s: path too long\n"), cmd);
				return(2);
			}
			strcpy(dest, target);
			strcat(dest, "/");
			strcat(dest, lc);
			/* try and symlink, let it gen error msg if can't do */
			if (symlink(source, dest) < 0) {
				if (errno == EEXIST)
					perror(dest);
				else if (access(lc, 0) < 0)
					perror(lc);
				else
					perror(dest);
				return (1);
			}
			return(0);
		}
		if (symlink(source, target) < 0) {
			if (errno == EEXIST)
				perror(target);
			else if (access(source, 0) < 0)
				perror(source);
			else
				perror(target);
			return (1);
		}
		return(0);
	}
	
	/*
	 * Make sure source file exists.
	 */
	 
	if (statf(source, &s1) < 0) {
		fprintf(stderr,
			gettxt("mv:5", "%s: cannot access %s\n"), cmd, source);
		return(1);
	}
	
	/* 
	 * Deal with directories.
	 * Now can move them, can't copy or link.
	 */
	
	if (ISDIR(s1)) {
		if (mve) {
			return(mv_dir(source, target));
		} else {
			fprintf(stderr,
				gettxt("mv:6", "%s : <%s> directory\n"), cmd, source);
			return(1);
		}
	}
	
	/*
	 * If it is a move command and we don't have write access 
	 * to the source's parent then goto s_unlink for the error
	 * message.
	 */

	if (mve) {
		struct stat spd;	/* stat of source's parent */
		int uid;		/* real uid */
		if (accs_parent(source, 2, &spd) == -1)
			goto s_unlink;
	
		/*
		 * If sticky bit set on source's parent, then move only when :
		 * superuser, source's owner, parent's owner, or source is writable.
		 * Otherwise, we won't be able to unlink source later and will be
		 * left with two links and an error exit from mv.
		 */
		if (spd.st_mode&S_ISVTX && (uid=getuid()) != 0 &&
		   s1.st_uid != uid && spd.st_uid != uid &&
		   access(source, 2)<0) {
			errno = EACCES;
			goto s_unlink;
		}
	}

	/* Deal with mv of symbolic links. */

	if (mve && ISLNK(s1)) {
		return(mv_link(source, target));
	}

	/*
	 * If stat fails, then the target doesn't exist,
	 * we will create a new target with default file type of regular.
 	 */	
	
	FTYPE(s2) = S_IFREG;
	
	if (statf(target, &s2) >= 0) {
		
		/*
		 * If target is a directory,
		 * make complete name of new file
		 * within that directory.
		 */

	    if (mve && (stat(target, &s3) >= 0)) {
		if (ISDIR(s3)) {
			if ((buf = malloc(strlen(target) + strlen(dname(source)) + 4)) == NULL) {
				fprintf(stderr,
			gettxt("mv:7", "%s: Insufficient memory to %s %s\n "),
					cmd,cmd,source);
				exit(3);
			}
			sprintf(buf, "%s/%s", target, dname(source));
			target = buf;
		}
	    } else {

		if (ISDIR(s2)) {
			if ((buf = malloc(strlen(target) + strlen(dname(source)) + 4)) == NULL) {
				fprintf(stderr,
			gettxt("mv:7", "%s: Insufficient memory to %s %s\n "),
					cmd,cmd,source);
				exit(3);
			}
			sprintf(buf, "%s/%s", target, dname(source));
			target = buf;
		}
	    }
		
		/*
		 * If filenames for the source and target are 
		 * the same and the inodes are the same, it is
		 * an error.
		 */
		
		if (statf(target, &s2) >= 0) {
			if (IDENTICAL(s1,s2)) {
				fprintf(stderr,
			    gettxt("mv:8", "%s: %s and %s are identical\n"),
					cmd, source, target);
				if (buf != NULL)
					free(buf);
				return(1);
			}
			
			/*
			 * Because copy doesn't unlink target,
			 * treat it separately.
			 */
			
			if(cpy)
				goto skip;
			
			/* 
			 * Prevent super-user from overwriting a directory
			 * structure with file of same name.
			 */
			 
			 if (mve && ISDIR(s2)) {
				fprintf(stderr,
		    gettxt("mv:9", "%s: Cannot overwrite directory %s\n"),
					cmd, target);
				if (buf != NULL)
					free(buf);
				return(1);
			}
			
			/*
			 * If the user does not have access to
			 * the target, ask him----if it is not
			 * silent and user invoked command 
			 * interactively.
			 */
			
			if (access(target, 2) < 0 
			 && isatty(fileno(stdin))
			 && !silent) {
				fprintf(stderr,
					gettxt("mv:10", "%s: %s: %o mode?"),
					cmd, target, FMODE(s2) & MODEBITS);
			
				/* Get response from user. Based on
				 * first character, make decision.
				 * Discard rest of line.
				 */
				
				i = c = getchar();
				while (c != '\n' && c != EOF)
					c = getchar();
				if (i != 'y') {
					if (buf != NULL)
						free(buf);
					return(1);
				}
			}
			
			/*
			 * Attempt to unlink the target.
			 */
			 
			 if (unlink(target) < 0) {
				fprintf(stderr,
				    gettxt("mv:11", "%s: cannot unlink %s\n"),
				    cmd, target);
				perror(cmd);
				if (buf != NULL)
					free(buf);
				return(1);
			}
		} else {
			FTYPE(s2) = S_IFREG;
		}
	}
skip:
	/* 
	 * Either the target doesn't exist,
	 * or this is a copy command ...
	 * If "mv" then try and rename.
	 */
	
	if (mve && (rename(source, target) == 0))
		return(0);

	if (cpy || link(source, target) < 0) {

		/*
		 * If link failed, and command was 'ln'
		 * send out appropriate error message.
		 */
		 
		if (lnk) {
			if(errno == EXDEV)
				fprintf(stderr,
			gettxt("mv:12", "%s: different file system\n"), cmd);
			else
				fprintf(stderr,
			    gettxt("mv:13", "%s: no permission for %s\n"),
					cmd, target);
			if (buf != NULL)
				free(buf);
			return(1);
		}
		
		/* 
		 * Attempt to open source for copy.
		 */
		 
		if((from = open(source, 0)) < 0) {
			fprintf(stderr,
				gettxt("mv:14", "%s: cannot open %s\n"),
				cmd, source);
			if (buf != NULL)
				free(buf);
			return (1);
		}
		
		/* 
		 * Save a flag to indicate target existed.
		 */
		
		oflg = access(target, 0) == -1;
		
		/* 
		 * Attempt to create a target.
		 */
		
		if((to = creat (target, 0666)) < 0) {
			fprintf(stderr,
				gettxt("mv:15", "%s: cannot create %s\n"),
				cmd, target);
			perror(cmd);
			if (buf != NULL)
				free(buf);
			close(from);
			return (1);
		}
		
		/*
		 * Block copy from source to target.
		 */
		 
		/*
		 * If we created a target,
		 * set its permissions to the source
		 * before any copying so that any partially copied
		 * file will have the source's permissions (at most)
		 * or umask permissions whichever is the most restrictive.
		 */
		 
		if (oflg)
			chmod(target, FMODE(s1));
		
		while((ct = read(from, fbuf, BLKSIZE)) != 0) {
			if (ct < 0) {
				fprintf(stderr,
		    gettxt("mv:33", "%s: read from %s failed\n"), cmd, source);

				close(from);
				close(to);

				/*
				 * If target is a regular file,
				 * unlink the bad file.
				 */
				 
				if (ISREG(s2))
					unlink(target);
				if (buf != NULL)
					free(buf);
				return (1);
			}
			if(write(to, fbuf, ct) != ct) {
				fprintf(stderr,
				    gettxt("mv:34", "%s: write to %s failed\n"),
				    cmd, target);
				close(from);
				close(to);

				/*
				 * If target is a regular file,
				 * unlink the bad file.
				 */
				 
				if (ISREG(s2))
					unlink(target);
				if (buf != NULL)
					free(buf);
				return (1);
			}
		}

		/*
		 * If it was a move, leave times alone.
		 */
		if (mve) {
			times = (struct utimbuf *) malloc((unsigned) sizeof(struct utimbuf) + 2);
			times->actime = s1.st_atime;
			times->modtime = s1.st_mtime;
			utime(target, times);
			free(times);
		}
		close(from), close(to);
		
	}
	
	/* 
	 * If it is a copy or a link,
	 * we don't have to remove the source.
	 */
	 
	if (!mve) {
		if (buf != NULL)
			free(buf);
		return (0);
	}
	
	/* 
	 * Attempt to unlink the source.
	 */
	 
	if (unlink(source) < 0) {
s_unlink:
		fprintf(stderr,
			gettxt("mv:11", "%s: cannot unlink %s\n"), cmd, source);
		perror(cmd);
		if (buf != NULL)
			free(buf);
		return(1);
	}
	if (buf != NULL)
		free(buf);
	return(0);
}


/* In addition to checking write access on parent dir, do a stat on it */
accs_parent(name, amode, sdirp)
register char *name;
register int amode;
register struct stat *sdirp;
{
	register c;
	register char *p, *q;
	char *buf;

	/*
	 * Allocate a buffer for parent.
	 */
	
	if ((buf = malloc(strlen(name) + 2)) == NULL) {
		fprintf(stderr,
		    gettxt("mv:17", "%s: Insufficient memory space.\n"),cmd);
		exit(3);
	}
	p = q = buf;
	
	/* 
	 * Copy name into buf and set 'q' to point to the last
	 * delimiter within name.
	 */
	 
	while (c = *p++ = *name++)
		if (c == DELIM)
			q = p-1;
	
	/*
	 * If the name had no '\' or was "\" then leave it alone,
	 * otherwise null the name at the last delimiter.
	 */
	 
	if (q == buf && *q == DELIM)
		q++;
	*q = NULL;
	
	/*
	 * Find the access of the parent.
	 * If no parent specified, use dot.
	 */
	 
	if ((c = access(buf[0] ? buf : DOT,amode)) == -1) {
	/* No write access to directory : no need to check sticky bit */
		free(buf);
		return(c);
	}

	/*
	 * Stat the parent : needed for sticky bit check.
	 * If stat fails, move() should fail the access,
	 * since we cannot proceed anyway.
	 */
	c = statf(buf[0] ? buf : DOT, sdirp);
	free(buf);
	return(c);
}

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

	/* 
	 * Return just the file name given the complete path.
	 * Like basename(1).
	 */
	 
	p = name;
	
	/*
	 * While there are characters left,
	 * set name to start after last
	 * delimiter.
	 */
	 
	while (*p)
		if (*p++ == DELIM && *p)
			name = p;
	return name;
}

usage()
{
	register char *opt;
	
	/*
	 * Display usage message.
	 */
	
	if (mve)
		opt = " [-f]";
	else if (lnk)
		opt = " [-fs]";
	else
		opt = "";		/* assume cp */
	 
	fprintf(stderr,
	    gettxt("mv:18", "Usage: %s%s f1 f2\n       %s%s f1 ... fn d1\n"), 
	    cmd, opt, cmd, opt);
	if (mve)
		fprintf(stderr, gettxt("mv:32", "       mv [-f] d1 d2\n"));
	if (lnk)
		fprintf(stderr, gettxt("mv:36", "       ln [-fs] f1\n"));
	exit(2);
}

/*
 * mv_dir()
 *
 * Move source to target, where source is a directory.
 *
 * Cases:
 *
 * 1) Target does not exist:
 *    - Use rename() to do move.
 * 2) Target is a directory:
 *    - See if last component of source exists under target.
 *      Error if so, otherwise use rename() to do move.
 * 3) Target is a sym link:
 *    - If sym link points to a directory check for name under
 *      that directory that will collide with name being moved.
 *      Else report error, "not a directory".
 */


mv_dir(source, target)
	char *source;
	char *target;
{
	char *lc;			/* last component of source */
	char dest[MAXPATHLEN + 2];	/* destination if target is directory */
	struct stat src, targ, st_d;
	register last;

	/* 
	 * While source or target have trailing 
	 * DELIM (/), remove them (unless only "/")
	 */

	while (((last = strlen(source)) > 1)
	    &&  (source[last-1] == DELIM))
		 source[last-1]=NULL;
	
	while (((last = strlen(target)) > 1)
	    &&  (target[last-1] == DELIM))
		 target[last-1]=NULL;
	
	/*
	 * Now create a pointer to the last component of the source path.
	 */
	
	lc = strrchr(source, '/');
	if (lc == (char *) NULL)
		lc = source;
	else {
		lc++;
		if (lc == '\0')
			lc = source;
	}

	/*
	 * Now generate appropriate error message for
	 * mv of "/", ".", or "..".
	 */

	if (EQ(lc, DELIM) || EQ(lc, DOT) || EQ(lc, DOTDOT)) {
		fprintf(stderr,
		    gettxt("mv:23", "%s: cannot rename %s\n"), cmd, lc);
		return(2);
	}

	/*
	 * Make sure source file exists.
	 */
	 
	if (lstat(source, &src) < 0) {
		fprintf(stderr,
			gettxt("mv:5", "%s: cannot access %s\n"), cmd, source);
		return(2);
	}

	/*
	 * If the target does not exist, we will simply use rename 
	 * to do the work.
	 */

	if (lstat(target, &targ) != 0) {
		if (rename(source, target) < 0) {
		    fprintf(stderr,
			gettxt("mv:23", "%s: cannot rename %s\n"), cmd, source);
		    return(2);
		}
		return(0);
	}

	/*
	 * If the target is not a directory then report an error.
	 * If target was a symbolic link we need to follow it.
	 *
	 * Note that the error message says target exists even
	 * though the stat call just failed.  This is because
	 * we are reporting that the sym link exists, even though
	 * it didn't point to an existing file.
	 */

	if (ISLNK(targ)) {
		if (stat(target, &targ) != 0) {
		    fprintf(stderr,
			gettxt("mv:21","%s: %s exists\n"), cmd, target);
		    return(2);
		}
	}

	if (!ISDIR(targ)) {
		fprintf(stderr, gettxt("mv:21","%s: %s exists\n"), cmd, target);
		return(2);
	}

	/*
	 * Now stat the source, and make sure source and target
	 * are not the same.  Want to follow link, so must stat
	 * this time (last was lstat).
	 */

	if (stat(source, &src) != 0) {
		fprintf(stderr,
			gettxt("mv:5", "%s: cannot access %s\n"), cmd, source);
		return(2);
	}

	if (IDENTICAL(src, targ)) {
		fprintf(stderr, gettxt("mv:8", "%s: %s and %s are identical\n"),
			cmd, source, target);
		return(2);
	}

	/*
	 * Now we have established that the target exists and it is a directory.
	 * (Or target is a sym link pointing to a directory that exists.)
	 * We must check for collision with an existing name under that directory.
	 *
	 * If there is a collision, the final destination must also be a
	 * directory.  If it is, then we will still try the rename because
	 * it will work if that destination is empty.
	 */

	if ((strlen(lc) + strlen(target) + 1) > (MAXPATHLEN - 1)) {
		fprintf(stderr, 
			gettxt("mv:37", "%s: path too long\n"), cmd);
		return(2);
	}

	strcpy(dest, target);
	strcat(dest, "/");
	strcat(dest, lc);

	/*
	 * If destination exists, we will only do the rename if
	 * it is a directory (which must be empty).
	 * Rename call will enforce that dir is empty.
	 */

	if (lstat(dest, &st_d) == 0) {
		if (!ISDIR(st_d) || (rename(source, dest) != 0)) {
		    fprintf(stderr, gettxt("mv:21","%s: %s exists\n"), cmd, dest);
		    return(2);
		}
		return(0);
	}

	/*
	 * Destination is target with last component of source added.
	 * This does not exist, so we can use rename to do the work.
	 */

	if (rename(source, dest) != 0) {
		perror(gettxt("mv:35","rename"));
		return(2);
	}

	return(0);
}

/*
 * mv_link()
 *
 * Move source to target where source is a sym link.
 *
 * Cases:
 *
 * 1) Target does not exist.
 *    - Use rename() to do work, will create target.
 *
 * 2) Target is a directory, or points to a directory.
 *    - Create destination using last component of source
 *      name.  If it exists we are going to clobber it
 *	so check access (on tty, no -f flag).  Finally
 *	build a sym link under directory and unlink source.
 *
 * 3) Target exists, but isn't a directory.
 *    - Check access (on tty, no -f flag).  Use rename()
 *      to do the work.
 */

mv_link(source, target)
	char *source;
	char *target;
{
	char *lc;			/* last component of source */
	char dest[MAXPATHLEN + 2];	/* destination if target is directory */
	struct stat src, targ, st_d;
	register last;


	/*
	 * See if target exists.  Let rename() do the
	 * work if it does not.
	 */

	if (lstat(target, &targ) != 0) {
		if (rename(source, target) < 0) {
		    fprintf(stderr,
			gettxt("mv:23", "%s: cannot rename %s\n"), cmd, source);
		    return(2);
		}
		return(0);
	}

	/*
	 * If target is a directory, see if any collisions with
	 * a file of same name as target that exists in the directory.
	 */

	if (ISDIR(targ)) {
		lc = strrchr(source, '/');
		if (lc == (char *) NULL)
			lc = source;
		else {
			lc++;
			if (lc == '\0')
				lc = source;
		}
		if ((strlen(lc) + strlen(target) + 1) > (MAXPATHLEN - 1)) {
			fprintf(stderr, 
				gettxt("mv:37", "%s: path too long\n"), cmd);
			return(2);
		}

		strcpy(dest, target);
		strcat(dest, "/");
		strcat(dest, lc);

		if (lstat(dest, &st_d) != 0) {
			if (rename(source, dest) != 0) {
				perror(gettxt("mv:35","rename"));
				return(2);
			}
			return(0);
		}
		if (can_clobber(dest, &st_d)) {
			if (unlink(dest) < 0) {
			    fprintf(stderr,
				gettxt("mv:11", "%s: cannot unlink %s\n"), cmd, source);
			    return(2);
			}
			return(new_symlink(source, dest));
		} else {
			return(2);
		}
	}

	/*
	 * Destination exists, see if it was a sym link.
	 */

	if (ISLNK(targ)) {
		if (stat(target, &targ) != 0) {
			/*
			 * Symlink that does not point at anything.
			 * Will clobber it, so ask if that is ok.
			 */
			if (can_clobber(target, &targ)) {
				if (unlink(target) < 0) {
				    fprintf(stderr,
					gettxt("mv:11", "%s: cannot unlink %s\n"), cmd, source);
				    return(2);
				}
				if (rename(source, target) != 0) {
					perror(gettxt("mv:35","rename"));
					return(2);
				}
				return(0);
			} else {
				return(2);
			}
		}
		/*
		 * Symlink that points to something.
		 * If a directory then have to fuss with moving link to subdirectory.
		 */
		 if (ISDIR(targ)) {
			lc = strrchr(source, '/');
			if (lc == (char *) NULL)
				lc = source;
			else {
				lc++;
				if (lc == '\0')
					lc = source;
			}

			if ((strlen(lc) + strlen(target) + 1) > (MAXPATHLEN - 1)) {
				fprintf(stderr, 
					gettxt("mv:37", "%s: path too long\n"), cmd);
				return(2);
			}

			strcpy(dest, target);
			strcat(dest, "/");
			strcat(dest, lc);

			if (lstat(dest, &st_d) != 0) {
				if (rename(source, dest) != 0) {
					perror(gettxt("mv:35","rename"));
					return(2);
				}
				return(0);
			}
			if (can_clobber(dest, &st_d)) {
				if (unlink(dest) < 0) {
				    fprintf(stderr,
					gettxt("mv:11", "%s: cannot unlink %s\n"), cmd, source);
				    return(2);
				}
				return(new_symlink(source, dest));
			}
			return(2);
		}
		/*
		 * Symlink points to something besides directory.
		 */
		if (can_clobber(target, &targ)) {
			if (unlink(target) < 0) {
				fprintf(stderr,
				    gettxt("mv:11", "%s: cannot unlink %s\n"), cmd, source);
				return(2);
			}
			if (rename(source, dest) != 0) {
				perror(gettxt("mv:35","rename"));
				return(2);
			}
			return(0);
		}
		return(2);
	}

	/*
	 * Destination exists, and is not a directory
	 * and does not point to a directory.
	 * Give user a chance to prevent move if destination
	 * is protected.
	 */

	if (can_clobber(target, &st_d)) {
		if (unlink(target) < 0) {
			fprintf(stderr,
			    gettxt("mv:11", "%s: cannot unlink %s\n"), cmd, source);
			return(2);
		}
		if (rename(source, target) != 0) {
			perror(gettxt("mv:35","rename"));
			return(2);
		}
		return(0);
	}
	return(2);
}

/*
 * can_clobber()
 *
 * If user is on tty, and didn't give "-f" flag,
 * give file mode and as permission to delete file.
 *
 * Return: 1 -- ok to delete.
 *         0 -- do NOT delete.
 */


can_clobber(target, sb)
	char *target;
	struct stat *sb;
{
	int i, c;

	if (access(target, 2) < 0 && isatty(fileno(stdin)) && !silent) {
		fprintf(stderr, gettxt("mv:10", "%s: %s: %o mode?"),
			cmd, target, (sb->st_mode & MODEBITS));
	
		/*
		 * Get response from user. Based on first character,
		 * make decision. Discard rest of line.
		 */
		
		i = c = getchar();
		while (c != '\n' && c != EOF)
			c = getchar();
		if (i != 'y')
			return(0);
	}
	return(1);
}

/*
 * new_symlink()
 *
 * Move a symlink to a new directory.  Have to create a new
 * symlink and unlink the present one.
 *
 */

new_symlink(source, target)
	char *source;
	char *target;
{
	char symln[MAXPATHLEN];
	register int m, i;
	struct stat src;


	if ((i = readlink(source, symln, sizeof(symln))) < 0) {
		perror(source);
		return(2);
	}
	symln[i] = '\0';
	if (lstat(source, &src) != 0) {
		fprintf(stderr, 
			gettxt("mv:5", "%s: cannot access %s\n"), cmd, source);
		return(2);
	}
	m = umask(~src.st_mode & MODEBITS);
	if (symlink(symln, target) < 0) {
		perror(target);
		return(2);
	}
	(void) umask(m);
	if (unlink(source) < 0) {
		(void) unlink(target);
		fprintf(stderr,
			gettxt("mv:11", "%s: cannot unlink %s\n"), cmd, source);
		return(2);
	}
	return(0);
}
