From mkamm@sbox.tu-graz.ac.at  Sun Feb 11 14:43:49 2001
Return-Path: <mkamm@sbox.tu-graz.ac.at>
Received: from ns1.tu-graz.ac.at (ns1.tu-graz.ac.at [129.27.2.3])
	by hub.freebsd.org (Postfix) with ESMTP id 6C69437B401
	for <FreeBSD-gnats-submit@freebsd.org>; Sun, 11 Feb 2001 14:43:48 -0800 (PST)
Received: from homebox.kammerhofer.org (isdn091.tu-graz.ac.at [129.27.240.91])
	by ns1.tu-graz.ac.at (8.9.3/8.9.3) with ESMTP id XAA07811
	for <FreeBSD-gnats-submit@freebsd.org>; Sun, 11 Feb 2001 23:43:39 +0100 (MET)
Received: (from mkamm@localhost)
	by homebox.kammerhofer.org (8.11.2/8.11.2) id f1BKmtE01611;
	Sun, 11 Feb 2001 21:48:55 +0100 (CET)
	(envelope-from mkamm)
Message-Id: <200102112048.f1BKmtE01611@homebox.kammerhofer.org>
Date: Sun, 11 Feb 2001 21:48:55 +0100 (CET)
From: mkamm@gmx.net
Reply-To: mkamm@gmx.net
To: FreeBSD-gnats-submit@freebsd.org
Subject: cp: options -i and -f do not work as documented
X-Send-Pr-Version: 3.2

>Number:         25015
>Category:       bin
>Synopsis:       cp(1) options -i and -f do not work as documented
>Confidential:   no
>Severity:       non-critical
>Priority:       medium
>Responsible:    freebsd-bugs
>State:          open
>Quarter:        
>Keywords:       
>Date-Required:  
>Class:          sw-bug
>Submitter-Id:   current-users
>Arrival-Date:   Sun Feb 11 14:50:02 PST 2001
>Closed-Date:    
>Last-Modified:  Wed May 21 20:50:58 UTC 2008
>Originator:     Martin Kammerhofer
>Release:        FreeBSD 4.2-STABLE i386
>Organization:
Universitt Graz
>Environment:
>Description:

I quote from "man cp":

##  -f  For each existing destination pathname, remove it and create a new
##      file, without prompting for confirmation regardless of its permis-
##      sions.
##
##  -i  Cause cp to write a prompt to the standard error output before
##      copying a file that would overwrite an existing file.

There are two bugs:

------------------------------
Bug #1:

Option -f is supposed to unlink targets before the copy takes
place. (This makes a great difference with respect to permissions and
especially with targets that are neither plain files nor directories.)

However cp(1) doesn't recognize the existence of targets which are
broken symlinks -- because of improper use of stat(2) instead of
lstat(2) -- and doesn't unlink them with the -f option.  This leads to
two kinds of errors:

  Bug #1.1:
  "cp -f source /dir/dlink" will ignore the -f option when dlink is a
  dangling symlink, i.e. points to nowhere. Instead of creating
  /dir/dlink as a copy of source the symlink will be followed and the
  copy of source will be created wherever dlink points to. (Provided that
  permissions allow it.) This can be a security risk.

  Bug #1.2:
  When recursively copying a special file (fifo, character device) or
  symlink onto a broken symlink the target can not be created because
  cp(1) misses the required unlink(2).

------------------------------
Bug #2:

Option -i is only respected when the source is a plain file. When the
source is a special file, fifo or symlink the target will be unlinked
without questions even if the target is plain file.
This bug will only show up in recursive mode (-R or -r).

>How-To-Repeat:

------------------------------
Bug #1.1
As anyuser:
$ ln -sf /hosts.equiv mumble.conf    # /hosts.equiv doesn't exist
Later as root:
# cp -f /usr/local/etc/mumble.conf.distrib mumble.conf

Now despite of option -f the symlink mumble.conf remains and a file in
the root directory has been created.

------------------------------
Bug #1.2
$ ln -sf /NOSUCH dslink
$ mkfifo myfifo
$ cp -RPf myfifo dslink
Will not work despite -f.

------------------------------
Bug #2

Note: /etc/termcap is assumed to be a symlink.
$ date > veryverypreciousfile
$ cp -iR /etc/termcap veryverypreciousfile

Now it's gone (replaced by a symlink) without questions
despite using option -i.

------------------------------

>Fix:

Index: cp.c
===================================================================
RCS file: /home/ncvs/src/bin/cp/cp.c,v
retrieving revision 1.24
diff -u -r1.24 cp.c
--- cp.c	1999/11/28 09:34:21	1.24
+++ cp.c	2001/02/11 18:10:40
@@ -355,7 +355,7 @@
 
 		switch (curr->fts_statp->st_mode & S_IFMT) {
 		case S_IFLNK:
-			if (copy_link(curr, !dne))
+			if (copy_link(curr))
 				badcp = rval = 1;
 			break;
 		case S_IFDIR:
@@ -397,7 +397,7 @@
 		case S_IFBLK:
 		case S_IFCHR:
 			if (Rflag) {
-				if (copy_special(curr->fts_statp, !dne))
+				if (copy_special(curr->fts_statp))
 					badcp = rval = 1;
 			} else {
 				if (copy_file(curr, dne))
@@ -406,7 +406,7 @@
 			break;
 		case S_IFIFO:
 			if (Rflag) {
-				if (copy_fifo(curr->fts_statp, !dne))
+				if (copy_fifo(curr->fts_statp))
 					badcp = rval = 1;
 			} else {
 				if (copy_file(curr, dne))
Index: extern.h
===================================================================
RCS file: /home/ncvs/src/bin/cp/extern.h,v
retrieving revision 1.9
diff -u -r1.9 extern.h
--- extern.h	1999/08/27 23:13:39	1.9
+++ extern.h	2001/02/11 18:11:30
@@ -47,10 +47,10 @@
 #include <sys/cdefs.h>
 
 __BEGIN_DECLS
-int	copy_fifo __P((struct stat *, int));
+int	copy_fifo __P((struct stat *));
 int	copy_file __P((FTSENT *, int));
-int	copy_link __P((FTSENT *, int));
-int	copy_special __P((struct stat *, int));
+int	copy_link __P((FTSENT *));
+int	copy_special __P((struct stat *));
 int	setfile __P((struct stat *, int));
 void	usage __P((void));
 __END_DECLS
Index: utils.c
===================================================================
RCS file: /home/ncvs/src/bin/cp/utils.c,v
retrieving revision 1.28
diff -u -r1.28 utils.c
--- utils.c	2000/10/10 01:48:18	1.28
+++ utils.c	2001/02/11 20:32:15
@@ -56,6 +56,44 @@
 
 #include "extern.h"
 
+#define YESNO "(y/n [n]) "
+
+/*
+ * If target does not exist return 0.  Otherwise if iflag ask for user
+ * confirmation and return 1 if user does not affirm.  If unlnk try to
+ * unlink target and return 2 on failure.
+ */
+
+static int
+check_unlink(unlnk)
+    int unlnk;
+{
+    struct stat ts;
+    int ch, checkch;
+    
+    if (lstat(to.p_path, &ts))
+	return (0);
+    if (iflag) {
+	(void)fprintf(stderr, "overwrite %s? %s", to.p_path, YESNO);
+	checkch = ch = getchar();
+	while (ch != '\n' && ch != EOF)
+	    ch = getchar();
+	if (checkch != 'y' && checkch != 'Y') {
+	    (void)fprintf(stderr, "not overwritten\n");
+	    return (1);
+	}
+    }
+    if (unlnk) {
+	/* remove existing destination file name, create a new file */
+	if (unlink(to.p_path)) {
+	    warn("cannot unlink %s", to.p_path);
+	    return (2);
+	}
+    }
+    return (0);
+}
+
+
 int
 copy_file(entp, dne)
 	FTSENT *entp;
@@ -63,7 +101,7 @@
 {
 	static char buf[MAXBSIZE];
 	struct stat to_stat, *fs;
-	int ch, checkch, from_fd, rcount, rval, to_fd, wcount, wresid;
+	int from_fd, rcount, rval, to_fd, wcount, wresid;
 	char *bufp;
 #ifdef VM_AND_BUFFER_CACHE_SYNCHRONIZED
 	char *p;
@@ -84,33 +122,16 @@
 	 * other choice is 666 or'ed with the execute bits on the from file
 	 * modified by the umask.)
 	 */
-	if (!dne) {
-#define YESNO "(y/n [n]) "
-		if (iflag) {
-			(void)fprintf(stderr, "overwrite %s? %s", 
-					to.p_path, YESNO);
-			checkch = ch = getchar();
-			while (ch != '\n' && ch != EOF)
-				ch = getchar();
-			if (checkch != 'y' && checkch != 'Y') {
-				(void)close(from_fd);
-				(void)fprintf(stderr, "not overwritten\n");
-				return (1);
-			}
-		}
-		
-		if (fflag) {
-		    /* remove existing destination file name, 
-		     * create a new file  */
-		    (void)unlink(to.p_path);
+	if (check_unlink(fflag)) {
+	    (void)close(from_fd);
+	    return (1);
+	}
+	if (fflag || dne) {
 		    to_fd = open(to.p_path, O_WRONLY | O_TRUNC | O_CREAT,
 				 fs->st_mode & ~(S_ISUID | S_ISGID));
-		} else 
+	} else
 		    /* overwrite existing destination file name */
 		    to_fd = open(to.p_path, O_WRONLY | O_TRUNC, 0);
-	} else
-		to_fd = open(to.p_path, O_WRONLY | O_TRUNC | O_CREAT,
-		    fs->st_mode & ~(S_ISUID | S_ISGID));
 
 	if (to_fd == -1) {
 		warn("%s", to.p_path);
@@ -204,9 +225,8 @@
 }
 
 int
-copy_link(p, exists)
+copy_link(p)
 	FTSENT *p;
-	int exists;
 {
 	int len;
 	char link[MAXPATHLEN];
@@ -216,26 +236,21 @@
 		return (1);
 	}
 	link[len] = '\0';
-	if (exists && unlink(to.p_path)) {
-		warn("unlink: %s", to.p_path);
+	if (check_unlink(1))
 		return (1);
-	}
 	if (symlink(link, to.p_path)) {
-		warn("symlink: %s", link);
+		warn("symlink: %s -> %s", to.p_path, link);
 		return (1);
 	}
 	return (0);
 }
 
 int
-copy_fifo(from_stat, exists)
+copy_fifo(from_stat)
 	struct stat *from_stat;
-	int exists;
 {
-	if (exists && unlink(to.p_path)) {
-		warn("unlink: %s", to.p_path);
+	if (check_unlink(1))
 		return (1);
-	}
 	if (mkfifo(to.p_path, from_stat->st_mode)) {
 		warn("mkfifo: %s", to.p_path);
 		return (1);
@@ -244,14 +259,11 @@
 }
 
 int
-copy_special(from_stat, exists)
+copy_special(from_stat)
 	struct stat *from_stat;
-	int exists;
 {
-	if (exists && unlink(to.p_path)) {
-		warn("unlink: %s", to.p_path);
+	if (check_unlink(1))
 		return (1);
-	}
 	if (mknod(to.p_path, from_stat->st_mode, from_stat->st_rdev)) {
 		warn("mknod: %s", to.p_path);
 		return (1);

>Release-Note:
>Audit-Trail:

From: Garrett Wollman <wollman@khavrinen.lcs.mit.edu>
To: mkamm@gmx.net
Cc: FreeBSD-gnats-submit@FreeBSD.ORG
Subject: bin/25015: cp: options -i and -f do not work as documented
Date: Mon, 12 Feb 2001 10:23:25 -0500 (EST)

 <<On Sun, 11 Feb 2001 21:48:55 +0100 (CET), mkamm@gmx.net said:
 
 > Option -f is supposed to unlink targets before the copy takes
 > place. (This makes a great difference with respect to permissions and
 > especially with targets that are neither plain files nor directories.)
 
 Actually, no.  The POSIX 1003.1-200x draft states:
 
     a. If dest_file exists, the following steps shall be taken:
 
 	i. If the -i option is in effect, the cp utility shall write a
 	prompt to the standard error and read a line from the standard
 	input. If the response is not affirmative, cp shall do nothing
 	more with source_file and go on to any remaining files.
 
 	ii. A file descriptor for dest_file shall be obtained by
 	performing actions equivalent to the open( ) function defined
 	in the System Interfaces volume of IEEE Std 1003.1-200x called
 	using dest_file as the path argument, and the
 	bitwise-inclusive OR of O_WRONLY and O_TRUNC as the oflag
 	argument.
 
 It goes on to say that if and only if (ii) fails, cp shall unlink the
 destination file and try again.
 
 Hmmm.  If dest_file is a dangling symbolic link, does it exist or not?
 It's not obvious to me; I'll file an Aardvark against the
 specification.
 
 -GAWollman
 
 
>Unformatted:
