Skip site navigation (1)Skip section navigation (2)
Date:      Sun, 11 Feb 2001 21:48:55 +0100 (CET)
From:      mkamm@gmx.net
To:        FreeBSD-gnats-submit@freebsd.org
Subject:   bin/25015: cp: options -i and -f do not work as documented
Message-ID:  <200102112048.f1BKmtE01611@homebox.kammerhofer.org>

next in thread | raw e-mail | index | archive | help

>Number:         25015
>Category:       bin
>Synopsis:       cp: 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:
>Originator:     Martin Kammerhofer
>Release:        FreeBSD 4.2-STABLE i386
>Organization:
Universität 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:
>Unformatted:


To Unsubscribe: send mail to majordomo@FreeBSD.org
with "unsubscribe freebsd-bugs" in the body of the message




Want to link to this message? Use this URL: <https://mail-archive.FreeBSD.org/cgi/mid.cgi?200102112048.f1BKmtE01611>