cvs commit: src/bin/cp cp.1

Bruce Evans bde at zeta.org.au
Thu Feb 24 18:37:27 GMT 2005


On Thu, 24 Feb 2005, Tom Rhodes wrote:

> trhodes     2005-02-24 00:06:22 UTC
>
>   FreeBSD src repository
>
>   Modified files:
>     bin/cp               cp.1
>   Log:
>   Note how cp(1) handles directories ending in "/."

I think you mean ones ending in "/".  Ones ending in "/." are similarly
mishandled, but this is not noted.  The strange English rule for putting
"." in quotes is very confusing here.  I don't know the exact rule.

>
>   PR:             75774
>   Submitted by:   Mike Meyer <mwm at mired.org> (original version)

This is a bug in cp.c, not in cp.1.  cp starts getting confused here:

% 		/*
% 		 * If we are in case (2) or (3) above, we need to append the
%                  * source name to the target name.
%                  */
% 		if (type != FILE_TO_FILE) {
% 			/*
% 			 * Need to remember the roots of traversals to create
% 			 * correct pathnames.  If there's a directory being
% 			 * copied to a non-existent directory, e.g.
% 			 *	cp -R a/dir noexist
% 			 * the resulting path name should be noexist/foo, not
% 			 * noexist/dir/foo (where foo is a file in dir), which
% 			 * is the case where the target exists.
% 			 *
% 			 * Also, check for "..".  This is for correct path
% 			 * concatenation for paths ending in "..", e.g.
% 			 *	cp -R .. /tmp
% 			 * Paths ending in ".." are changed to ".".  This is
% 			 * tricky, but seems the easiest way to fix the problem.
% 			 *
% 			 * XXX
% 			 * Since the first level MUST be FTS_ROOTLEVEL, base
% 			 * is always initialized.
% 			 */
% 			if (curr->fts_level == FTS_ROOTLEVEL) {
% 				if (type != DIR_TO_DNE) {
% 					p = strrchr(curr->fts_path, '/');
% 					base = (p == NULL) ? 0 :
% 					    (int)(p - curr->fts_path + 1);
%
% 					if (!strcmp(&curr->fts_path[base],
% 					    ".."))
% 						base += 1;
% 				} else
% 					base = curr->fts_pathlen;
% 			}

It needs to strip to the basename, but for source file names ending in a
slash, it strips everything.  This is handled correctly in basename(3).
OTOH, cp needs something special for "foo/.." and basename() doesn't do
the right thing for that (it gives "..").  Somehow stripping "foo/.."
to "." doesn't give the same result as stripping "foo/." to ".".

There is an interesting subcase of this bug for symlinks.  After:

    mkdir /tmp/z
    cd /tmp/z
    mkdir a1 b
    ln -s a1 a

"cp -R a/ b" copies nothing but succeeds (wrong), but part of
"cp -pR a/ b" thinks that the other part copied something and fails
trying to change the attributes:

%%%
cp: utimes: b/a: No such file or directory
cp: chown: b/a: No such file or directory
cp: chmod: b/a: No such file or directory
cp: chflags: b/a: No such file or directory
%%%

Then after putting a file in "a1", "cp -R a/ b" just copies the wrong
thing (it follows the symlink OK but then copies the contents of the
directory pointed to by the symlink when it should copy the directory
itself) and then "cp -pR" doesn't try to change the attributes of a
nonexistent file.

The case of a trailing slash is most important for symlinks since it is
the only way to get "cp -R" to follow the symlink.

Bruce


More information about the cvs-src mailing list