Bug 14920

Summary: install(1) hangs when intalling files same directory.
Product: Base System Reporter: okimoto <okimoto>
Component: binAssignee: freebsd-bugs (Nobody) <bugs>
Status: Closed FIXED    
Severity: Affects Only Me    
Priority: Normal    
Version: 3.3-RELEASE   
Hardware: Any   
OS: Any   

Description okimoto 1999-11-16 12:20:00 UTC
Unable kill install(1), when installing files 
to same directory.

Fix: 

install(1) should detect original source directory and
target directory is same or not, I think.
How-To-Repeat: 1) touch ./foo
2) install ./foo .
Comment 1 Alexander Langer 1999-11-17 20:51:32 UTC
I've done a patch to xinstall, that detects if the source-file is the
same as the destination file, also if they are in the same directory:

alex:~ $ touch foo bar
alex:~ $ install foo bar .
install: foo and ./foo are the same file
alex:~ $ install foo bar ../../home/alex
install: foo and ../../home/alex/foo are the same file
alex:~ $ install foo bar /usr/home/alex 
install: foo and /usr/home/alex/foo are the same file
alex:~ $ install foo bar test/..       
install: foo and test/../foo are the same file
alex:~ $ 


Here is the patch:

--- xinstall.c.old	Wed Nov 17 20:32:40 1999
+++ xinstall.c	Wed Nov 17 21:48:05 1999
@@ -90,7 +90,7 @@
 int debug, docompare, docopy, dodir, dopreserve, dostrip, nommap, verbose;
 int mode = S_IRWXU|S_IRGRP|S_IXGRP|S_IROTH|S_IXOTH;
 char *group, *owner, pathbuf[MAXPATHLEN];
-char pathbuf2[MAXPATHLEN];
+char pathbuf2[MAXPATHLEN], pathbuf3[MAXPATHLEN];
 
 #define	DIRECTORY	0x01		/* Tell install it's a directory. */
 #define	SETFLAGS	0x02		/* Tell install to set flags. */
@@ -105,6 +105,7 @@
 void	strip __P((char *));
 void	usage __P((void));
 int	trymmap __P((int));
+char *	basename __P((char *));
 
 #define ALLOW_NUMERIC_IDS 1
 #ifdef ALLOW_NUMERIC_IDS
@@ -128,7 +129,7 @@
 	int argc;
 	char *argv[];
 {
-	struct stat from_sb, to_sb;
+	struct stat from_sb, to_sb, fileto_sb;
 	mode_t *set;
 	u_long fset;
 	u_int iflags;
@@ -222,8 +223,28 @@
 
 	no_target = stat(to_name = argv[argc - 1], &to_sb);
 	if (!no_target && (to_sb.st_mode & S_IFMT) == S_IFDIR) {
-		for (; *argv != to_name; ++argv)
-			install(*argv, to_name, fset, iflags | DIRECTORY);
+		for (; *argv != to_name; ++argv) {
+			if (stat(*argv, &from_sb))
+				err(EX_OSERR, "%s", *argv);
+			snprintf(pathbuf3, MAXPATHLEN,
+			    "%s%s%s", to_name,
+			    to_name[strlen(to_name) - 1] == '/' ? "" : "/",
+			    basename(*argv));
+			if (stat(pathbuf3, &fileto_sb) && errno != ENOENT)
+				err(EX_OSERR, "%s", pathbuf3);
+			else if (errno == ENOENT)
+				goto target_ok;
+			if (!S_ISREG(fileto_sb.st_mode)) {
+				errno = EFTYPE;
+				err(EX_OSERR, "%s", pathbuf3);
+			}
+			if (fileto_sb.st_dev == from_sb.st_dev &&
+			    fileto_sb.st_ino == from_sb.st_ino)
+				errx(EX_USAGE,
+				    "%s and %s are the same file", *argv, pathbuf3);
+		target_ok:
+			install(*argv, pathbuf3, fset, iflags);
+	    }
 		exit(EX_OK);
 		/* NOTREACHED */
 	}
@@ -736,4 +757,19 @@
 		return (1);
 #endif
 	return (0);
+}
+
+/*
+ * basename --
+ *	return the filename of a file whose absolut path is given
+ *	as pointer to char.
+ */
+char *
+basename(name)
+	char *name;
+{
+	char *slash;
+
+	slash = strrchr(name, '/');
+	return (slash == NULL ? name : slash + 1);
 }
Comment 2 Sheldon Hearn 1999-11-18 10:27:28 UTC
On Tue, 16 Nov 1999 04:12:20 PST, okimoto@mrit.mei.co.jp wrote:

> Unable kill install(1), when installing files 
> to same directory.
> >How-To-Repeat:
> 1) touch ./foo
> 2) install ./foo .

This doesn't cause a problem in CURRENT.  Could you please test this for
3.3-STABLE.  If it's still a problem there, then we'll need to
investigate the difference between RELENG_3 and HEAD and figure out what
fix needs to be backported.

Ciao,
Sheldon.
Comment 3 okimoto 1999-11-18 11:32:59 UTC
Thank you for repling my report.

But, I have received some mails from "alex@cichlids.com",
maybe a menbar of freebsd-gnats-submit. And this problem
has solved.

He said this problem was  3.3-RELEASE case. But,
install(1) should detect if the source-file is the
same as the destination file, he also thought. 
So, he made a patch for "xinstall.c", and it's good.

Thank you, Mr.Alexander. 
And thank you, Mr.Sheldon. 

# ..though, I should report this to somewhere?

---
Yoshiyuki OKIMOTO
Comment 4 Alexander Langer 1999-11-18 14:49:55 UTC
ok. This patch seems nice.
Please discard the old one.

I also fixed a /dev/null error. Please see the thread on freebsd-bugs
about this.

Sheldon suggested to use realpath(), so I did.

Usage-log:

alex:~ $ mkdir test
mkdir: test: File exists
alex:~ $ touch foo; /usr/obj/usr/src/usr.bin/xinstall/xinstall foo
test
alex:~ $ touch foo; /usr/obj/usr/src/usr.bin/xinstall/xinstall foo
test/foo
alex:~ $ touch foo; /usr/obj/usr/src/usr.bin/xinstall/xinstall foo
test/.. 
xinstall: foo and /usr/home/alex/foo are the same file
alex:~ $ touch foo; /usr/obj/usr/src/usr.bin/xinstall/xinstall foo .      
xinstall: foo and /usr/home/alex/foo are the same file
alex:~ $ touch foo; /usr/obj/usr/src/usr.bin/xinstall/xinstall foo ./
xinstall: foo and /usr/home/alex/foo are the same file
alex:~ $ touch foo; /usr/obj/usr/src/usr.bin/xinstall/xinstall foo
./../alex
xinstall: foo and /usr/home/alex/foo are the same file
alex:~ $ touch foo; /usr/obj/usr/src/usr.bin/xinstall/xinstall ./foo
./../alex
xinstall: ./foo and /usr/home/alex/foo are the same file
alex:~ $ touch foo; /usr/obj/usr/src/usr.bin/xinstall/xinstall test           
test          test.scm      testdatei     testwort.bak  
test.c        testbla       testwort      
alex:~ $ touch foo; /usr/obj/usr/src/usr.bin/xinstall/xinstall
test/bar test
xinstall: test/bar and /usr/home/alex/test/bar are the same file
alex:~ $ touch foo; /usr/obj/usr/src/usr.bin/xinstall/xinstall
test/bar test/..
alex:~ $ touch foo; /usr/obj/usr/src/usr.bin/xinstall/xinstall
/dev/null test  
xinstall: Cannot install /dev/null to a directory
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
YES, I did it. I changed the error-msg. But I think, it's ok.

alex:~ $ rm -rf bar ; /usr/obj/usr/src/usr.bin/xinstall/xinstall
/dev/null bar ; ls -l bar
-rwxr-xr-x  1 alex  alex  0 18 Nov 15:45 bar*

Patch:

--- xinstall.c.old	Wed Nov 17 20:32:40 1999
+++ xinstall.c	Thu Nov 18 15:48:12 1999
@@ -90,7 +90,7 @@
 int debug, docompare, docopy, dodir, dopreserve, dostrip, nommap, verbose;
 int mode = S_IRWXU|S_IRGRP|S_IXGRP|S_IROTH|S_IXOTH;
 char *group, *owner, pathbuf[MAXPATHLEN];
-char pathbuf2[MAXPATHLEN];
+char pathbuf2[MAXPATHLEN], pathbuf3[MAXPATHLEN];
 
 #define	DIRECTORY	0x01		/* Tell install it's a directory. */
 #define	SETFLAGS	0x02		/* Tell install to set flags. */
@@ -319,7 +319,7 @@
 	u_long fset;
 	u_int flags;
 {
-	struct stat from_sb, to_sb;
+	struct stat from_sb, to_sb, fileto_sb;
 	int devnull, from_fd, to_fd, serrno;
 	char *p, *old_to_name = 0;
 
@@ -327,25 +327,35 @@
 		fprintf(stderr, "install: invoked without -C for %s to %s\n",
 			from_name, to_name);
 
+	if (strcmp(from_name, _PATH_DEVNULL) == 0) {
+		from_sb.st_flags = 0;	/* XXX */
+		devnull = 1;
+	} else 
+		devnull = 0;
 	/* If try to install NULL file to a directory, fails. */
-	if (flags & DIRECTORY || strcmp(from_name, _PATH_DEVNULL)) {
+	if (flags & DIRECTORY && devnull)
+		errx(EX_USAGE, "Cannot install %s to a directory",
+		    _PATH_DEVNULL);
+
+	if (flags & DIRECTORY) {
+		if (realpath(to_name, pathbuf2) == NULL)
+			errx(EX_OSERR, "%s", pathbuf2);
+		(void)snprintf(pathbuf3, sizeof(pathbuf3),
+		   "%s/%s", pathbuf2,
+		    (p = strrchr(from_name, '/')) ? ++p : from_name);
+		to_name = pathbuf3; /* target path is in pathbuf3 */
 		if (stat(from_name, &from_sb))
 			err(EX_OSERR, "%s", from_name);
+		if (stat(to_name, &fileto_sb) && errno != ENOENT)
+			err(EX_OSERR, "%s", to_name);
 		if (!S_ISREG(from_sb.st_mode)) {
 			errno = EFTYPE;
 			err(EX_OSERR, "%s", from_name);
 		}
-		/* Build the target path. */
-		if (flags & DIRECTORY) {
-			(void)snprintf(pathbuf, sizeof(pathbuf), "%s/%s",
-			    to_name,
-			    (p = strrchr(from_name, '/')) ? ++p : from_name);
-			to_name = pathbuf;
-		}
-		devnull = 0;
-	} else {
-		from_sb.st_flags = 0;	/* XXX */
-		devnull = 1;
+		if (fileto_sb.st_dev == from_sb.st_dev &&
+		    fileto_sb.st_ino == from_sb.st_ino)
+			errx(EX_USAGE,
+			     "%s and %s are the same file", from_name, pathbuf3);
 	}
 
 	if (docompare) {
#### diff ends here

So long,

Alex
-- 
I doubt, therefore I might be.
Comment 5 Mike Barcroft freebsd_committer freebsd_triage 2001-07-21 04:06:27 UTC
State Changed
From-To: open->closed


This problem was apparently fixed in 4.0-RELEASE.  See sheldonh's 
comments for details.