Minix1.5/commands/mv.c

Compare this file to the similar file:
Show the results in this format:

/* mv - move files		Author: Adri Koppes */

/* 4/25/87 - J. Paradis		Bug fixes for directory handling
 * 3/15/88 - P. Housel		More directory bug fixes
 * 1/21/89 - B. Evans		Allow owner to move non-writable file.
 *				Don't allow move to non-writable file.
 *				Delete target file in case of moving to dir.
 *				Requires making file readable before copy.
 *				Fix up mode after copy (cp uses the target
 *				mode, if any, and strips high bits).
 *				Use adequate size for dotdot buffer.
 * Jan  90 - B. Evans		The directory above /foo was calculated as
 *				"" instead of "/", so "mv /bin /bin0" left
 *				the old bin/. and bin/.. links in the
 *				current directory or nowhere.
 *
 *				BUGS.
 *				There are race conditions all over.
 *				Error messages are not specific.
 *				There are magic sizes 64 and 128 instead of
 *				sizes related to PATH_MAX.
 */

#include <sys/types.h>
#include <sys/stat.h>
#include <signal.h>
#include <string.h>

struct stat st, st1;

main(argc, argv)
int argc;
char **argv;
{
  char *destdir;

  if (argc < 3) {
	std_err("Usage: mv file1 file2 or mv dir1 dir2 or mv file1 file2 ... dir\n");
	exit(1);
  }
  if (argc == 3) {
	if (stat(argv[1], &st)) {
		std_err("mv: ");
		std_err(argv[1]);
		std_err(" doesn't exist\n");
		exit(1);
	}
	move(argv[1], argv[2]);
  } else {
	destdir = argv[--argc];
	if (stat(destdir, &st)) {
		std_err("mv: target directory ");
		std_err(destdir);
		std_err(" doesn't exist\n");
		exit(1);
	}
	if ((st.st_mode & S_IFMT) != S_IFDIR) {
		std_err("mv: target ");
		std_err(destdir);
		std_err(" not a directory\n");
		exit(1);
	}
	while (--argc) move(*++argv, destdir);
  }
  exit(0);
}

move(old, new)
char *old, *new;
{
  int retval;
  char name[64];
  char parent[64];
  char *oldbase;
  int i;

  if ((oldbase = strrchr(old, '/')) == 0)
	oldbase = old;
  else
	++oldbase;

  /* It's too dangerous to fool with "." or ".." ! */
  if ((strcmp(oldbase, ".") == 0) || (strcmp(oldbase, "..") == 0)) {
	cant(old);
  }

  /* Don't move a non-existent file. */
  if (stat(old, &st) != 0) cant(old);

  /* If source is not writable, don't move it unless user owns it. */
  if (access(old, 2) != 0 && st.st_uid != getuid()) cant(old);

  /* If target is a directory, form its full name. The error of moving
   * a directory to itself is caught later. */
  if (stat(new, &st1) == 0 && (st1.st_mode & S_IFMT) == S_IFDIR) {
	if ((strlen(oldbase) + strlen(new) + 2) > 64) cant(old);
	strcpy(name, new);
	strcat(name, "/");
	strcat(name, oldbase);
	new = name;
  }

  /* If target exists, do various overwriting checks. */
  if (stat(new, &st1) == 0) {

	/* Don't move a file to itself. */
	if (st.st_dev == st1.st_dev && st.st_ino == st1.st_ino) cant(old);

	/* Don't move on top of a directory. */
	if ((st1.st_mode & S_IFMT) == S_IFDIR) cant(old);

	/* Don't move on top of a non-writable file. */
	if (access(new, 2) != 0) cant(old);
  }
  strcpy(parent, new);

  for (i = (strlen(parent) - 1); i > 0; i--) {
	if (parent[i] == '/') break;
  }

  if (i == 0) {
	if (parent[0] == '/')
		parent[1] = '\0';	/* parent of "/bin" is "/", not "." */
	else
		strcpy(parent, ".");
  } else {
	/* Null-terminate name at last slash */
	parent[i] = '\0';
  }

  /* Prevent moving a directory into its own subdirectory */
  if ((st.st_mode & S_IFMT) == S_IFDIR) {
	char lower[128];
	short int prevdev = -1;
	unsigned short previno;

	strcpy(lower, parent);
	while (1) {
		if (stat(lower, &st1) || (st1.st_dev == st.st_dev
					&& st1.st_ino == st.st_ino))
			cant(old);

		/* Stop at root */
		if (st1.st_dev == prevdev && st1.st_ino == previno) break;
		prevdev = st1.st_dev;
		previno = st1.st_ino;
		strcat(lower, "/..");
	}
  }

  /* If target exists, it is a file. Delete it so that following link()
   * works except across file systems, to avoid copying. */
  if (stat(new, &st1) == 0) unlink(new);

  if (link(old, new))
	if ((st.st_mode & S_IFMT) != S_IFDIR) {
		switch (fork()) {
		    case 0:
			if (!(st.st_mode & S_IRUSR))
				chmod(old, st.st_mode | S_IRUSR);
			setgid(getgid());
			setuid(getuid());
			execl("/bin/cp", "cp", old, new, (char *) 0);
			execl("/usr/bin/cp", "cp", old, new, (char *) 0);
			cant(old);
		    case -1:
			std_err("mv: can't fork\n");
			exit(1);
		    default:
			wait(&retval);
			if (!(st.st_mode & S_IRUSR)) chmod(old, st.st_mode);
			if (retval) cant(old);
			chmod(new, st.st_mode);
		}
	} else
		cant(old);

  /* If this was a directory that we moved, then we have * to update
   * its ".." entry (in case it was moved some- * where else in the
   * tree...) */
  if ((st.st_mode & S_IFMT) == S_IFDIR) {
	char dotdot[64 + 3];

	/* Unlink the ".." entry */
	strcpy(dotdot, new);
	strcat(dotdot, "/..");
	unlink(dotdot);

	/* Now link it to its parent */
	link(parent, dotdot);
  }
  utime(new, &st.st_atime);
  unlink(old);
}

cant(name)
char *name;
{
  std_err("mv: can't move ");
  std_err(name);
  std_err("\n");
  exit(1);
}