Subject: bin/3202: mv for directories wierdness
To: None <>
From: Paul Boven <>
List: netbsd-bugs
Date: 02/09/1997 18:08:20
>Number:         3202
>Category:       bin
>Synopsis:       mv foo/ bar/ fails, while mv foo bar/ works (foo is a dir)
>Confidential:   no
>Severity:       non-critical
>Priority:       low
>Responsible:    bin-bug-people (Utility Bug People)
>State:          open
>Class:          sw-bug
>Submitter-Id:   net
>Arrival-Date:   Sun Feb  9 09:20:00 1997
>Originator:     Paul Boven
Paul Boven, <>  PE1NUT  QRV 145.575 JO32KF
          Lynx users have a "Right to follow a link", too!
>Release:        Current of 6 feb 1997
System: NetBSD 1.2B NetBSD 1.2B (ELC) #4: Sat Jan 18 17:44:10 CET 1997 sparc

When you try to mv something with a trailing / in the pathname, rename()
will return EISDIR because namei() doesn't accept a trailing / unless the
mode is LOOKUP. Rename() uses RENAME and DELETE. This behaviour is NOT
documented in the rename()-manpage, wich states that EISDIR only occurs
when "to is a directory, but from is not a directory". 
mkdir foo bar; mv foo/ bar; mv foo bar
The first mv will (erronously) return the error "Is a directory" (EISDIR). 
Other commands (ls, mkdir e.g.) don't mind a trailing /.
I find this quite annoying because filename-completion in e.g. bash
will give you the / after the dir-name.

The way to fix this depends on what you consider the proper way for mv
and namei to behave. Assuming that namei is correct and should reject
RENAME and DELETE calls with a trailing /, then one has to fix mv to
strip these. The actual fix depens on how mv should behave. With foo
and bar both a directory, there are two options:
1. delete bar and rename foo as bar, unless bar is not empty.
2. make foo a subdirectory of bar.

The mv manpage implies BOTH options, and should be fixed to represent
the fixed situation:

     It is an error for either the source operand or the destination path to
     specify a directory unless both do.

This implies the first option. But it is also clearly wrong, giving a file
as source operand and a directory as destination will cause mv to move the
file into the directory. The author of this manpage probably got a little
confused by the behaviour of rename() (to wich mv is just a front-end)
and where this is indeed in error. I suggest changing the manpage here to:

	It is an error for the source operand to specify a directory unless
	the destination is a directory too.

The manpage also states:
[The second form is assumed when the last operand names an already existing

     In its second form, mv moves each file named by a source operand to a
     destination file in the existing directory named by the directory
     operand.  The destination path for each operand is the pathname produced
     by the concatenation of the last operand, a slash, and the final pathname
     component of the named file.

I have taken this to be the intended way for mv to behave, and the only
fix needed to mv then is stripping trailing / from the source-operands.
(The target never has a trailing / in the rename()-call because the name
of the source will be appended. The target-operand to mv always gets a /
appended before appending source, even if there already is one, I fixed that

diff mv.c mv.c.orig
< 	if (*(endp-1) != '/') {
< 		*endp++ = '/';
< 		++baselen;
< 	}
> 	*endp++ = '/';
> 	++baselen;
< 		len = strlen(*argv);
< 		len--;
< 		if (( (*argv)[len] == '/') && (len > 0))  
< 			(*argv)[len] = NULL;

I also suggest fixing the rename()-manpage to state that EISDIR is also
the error-message given when either soure or target have a trailing /.

Happy hacking, Paul.