[comp.os.minix] Serious "mv" bug, and fix

housel@ea.ecn.purdue.edu (Peter S. Housel) (03/16/88)

	Two weeks ago, I accidentally mv'd the contents of my home directory
into a subdirectory of a subdirectory of my home directory. The effect of
this was to make about 2/3 of the 10 Meg worth of files on my hard drive
inaccessible. It took about six or seven hours of intensive hacking to get
most of these files back, but many will have to be rewritten from scratch. 

	The patch below keeps this from happening, by checking to make
sure that the destination of a directory move is not in one of the
source's subdirectories. Also, the code allready checked to make sure
you couldn't rename ".." or ".", but going through the back door (i.e.
"./." or "dir/..") was still possible.

	One more thing: it would really be nice if fsck could handle
putting unref'ed inodes into a "lost+found" directory.

-Peter S. Housel-
housel@ei.ecn.purdue.edu	...!pur-ee!housel

Apply the following patch (to the 1.2 version of mv.c) using fix. Be
sure to "chown root mv; chmod 4755 mv". (Note- the patch is only about
40 bytes shorter than the entire mv.c)
---------------------------cut here---------------------------------------
3a4
>  * 3/15/88 - P. Housel		More directory bug fixes
9c10,11
< int     error = 0;
---
> extern char *rindex();
> 
49d50
<     if (error) exit (1);
59,61c60,70
< 
<     /* It's too dangerous to fool with "." or ".." ! */
<     if((strcmp(old, ".") == 0) || (strcmp(old, "..") == 0)) {
---
>     char    parent[64];
>     char    *oldbase;
>     int     i;
> 
>     if((oldbase = rindex(old, '/')) == 0)
> 	oldbase = old;
>     else
> 	++oldbase;
> 
>     /* It's too dangerous to fool with "." or ".." ! */
>     if((strcmp(oldbase, ".") == 0) || (strcmp(oldbase, "..") == 0)) {
70,87c79,126
<     if (!stat (new, &st))
< 	if((st.st_mode & S_IFMT) != S_IFDIR)
< 	    unlink (new);
<     else {
< 	char *p, *rindex();
< 
< 	if ((strlen(old) + strlen(new) + 2) > 64) {
< 		cant(old);
< 		error++;
< 		return;
< 	}
< 	strcpy(name, new);
< 	strcat(name, "/");
< 	p = rindex(old, '/');
< 	strcat(name, p ? p : old);
< 	new = name;
<     }
<     stat (old, &st);
---
>     if (!stat (new, &st1))
> 	if((st1.st_mode & S_IFMT) != S_IFDIR)
> 	    unlink (new);
>     else {
> 	if ((strlen(oldbase) + strlen(new) + 2) > 64) {
> 		cant(old);
> 		}
> 	strcpy(name, new);
> 	strcat(name, "/");
> 	strcat(name, oldbase);
> 	new = name;
>     }
> 
>     strcpy(parent, new);
> 
>     for(i = (strlen(parent) - 1); i > 0; i--) {
> 	if(parent[i] == '/') break;
>     }
> 
>     if(i == 0) {
> 	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, "/..");
> 	}
>     }
> 
112,161c151,175
< 	int	i;
< 	char	parent[64], dotdot[64];
< 
< 	strcpy(parent, new);
< 
< 	/* Determine the name for the parent of
< 	** the new name by counting back until we
< 	** hit a '/' or the begining of the string
< 	*/
< 
< 	for(i = (strlen(parent) - 1); i > 0; i--) {
< 	    if(parent[i] == '/') break;
< 	}
< 
< 	/* If there are no slashes, then the name is
< 	** in the current directory, so its parent
< 	** is ".".  Otherwise, the parent is the name
< 	** up to the last slash.
< 	*/
< 	if(i == 0) {
< 		strcpy(parent, ".");
< 	}
< 	else {
< 		/* null-terminate name at last slash */
< 		parent[i] = '\0';
< 	}
< 
< 	/* 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);
< }
< 
< 
---
> 	char	dotdot[64];
> 
> 	/* 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);
> }
> 
>