Subject: bin/3608: ln(1) always follows symlinks to directories when creating links
To: None <gnats-bugs@gnats.netbsd.org>
From: John Kohl <jtk@kolvir.arlington-heights.ma.us>
List: netbsd-bugs
Date: 05/11/1997 20:37:47
>Number:         3608
>Category:       bin
>Synopsis:       ln(1) always follows symlinks to directories when creating links
>Confidential:   no
>Severity:       non-critical
>Priority:       medium
>Responsible:    bin-bug-people (Utility Bug People)
>State:          open
>Class:          change-request
>Submitter-Id:   net
>Arrival-Date:   Sun May 11 17:50:00 1997
>Last-Modified:
>Originator:     John Kohl
>Organization:
NetBSD Kernel Hackers `R` Us
>Release:        NetBSD-1.2E (1997/05/11)
>Environment:
	
System: NetBSD pattern.arlington-heights.ma.us 1.2E NetBSD 1.2E (PATTERN) #80: Sun May 11 08:26:40 EDT 1997 jtk@pattern.arlington-heights.ma.us:/u4/sandbox/src/sys/arch/i386/compile/PATTERN i386


>Description:
	ln(1) uses stat(2) to determine the file types pointed to by
existing file names.  It doesn't have an option to treat symlinks as
distinguishable objects.  For example, you might wish this command
sequence to replace the symlink with a new symlink, but it
generates a new symbolic link in the subdir `foo':

	mkdir foo
	ln -s foo bar
	ln -sf quux bar

symlink(7) suggests that -h be used for commands which operate in a
non-recursive mode on file objects.

>How-To-Repeat:
>Fix:
Here's a suggested implementation.  One corner case that might be
contentious is the case of a target name which is a symlink pointing to
a directory, and a command of the form:
	mkdir foo
	ln -s foo target_dir
	ln -h file1 file2 ... target_dir

My sample implementation treats this as an error, and simulates an
ENOTDIR return.

cvs diff: Diffing .
Index: ln.1
===================================================================
RCS file: /u3/cvsroot/src/bin/ln/ln.1,v
retrieving revision 1.1.1.1
diff -c -r1.1.1.1 ln.1
*** ln.1	1996/03/29 01:55:35	1.1.1.1
--- ln.1	1997/05/12 00:13:57
***************
*** 73,78 ****
--- 73,84 ----
  Unlink any already existing file, permitting the link to occur.
  .It Fl s
  Create a symbolic link.
+ .It Fl h
+ If the
+ .Ar target_file
+ or
+ .Ar target_dir
+ is a symbolic link, do not follow it.
  .El
  .Pp
  By default
Index: ln.c
===================================================================
RCS file: /u3/cvsroot/src/bin/ln/ln.c,v
retrieving revision 1.1.1.1
diff -c -r1.1.1.1 ln.c
*** ln.c	1996/03/29 01:55:35	1.1.1.1
--- ln.c	1997/05/12 00:37:18
***************
*** 59,64 ****
--- 59,65 ----
  
  int	dirflag;			/* Undocumented directory flag. */
  int	fflag;				/* Unlink existing files. */
+ int	lflag;				/* Check new name for symlink first. */
  int	sflag;				/* Symbolic, not hard, link. */
  					/* System link call. */
  int (*linkf) __P((const char *, const char *));
***************
*** 75,81 ****
  	int ch, exitval;
  	char *sourcedir;
  
! 	while ((ch = getopt(argc, argv, "Ffs")) != -1)
  		switch (ch) {
  		case 'F':
  			dirflag = 1;	/* XXX: deliberately undocumented. */
--- 76,82 ----
  	int ch, exitval;
  	char *sourcedir;
  
! 	while ((ch = getopt(argc, argv, "hnFfs")) != -1)
  		switch (ch) {
  		case 'F':
  			dirflag = 1;	/* XXX: deliberately undocumented. */
***************
*** 86,91 ****
--- 87,96 ----
  		case 's':
  			sflag = 1;
  			break;
+ 		case 'h':
+ 		case 'n':		/* GNU compat; undocumented */
+ 			lflag = 1;
+ 			break;
  		case '?':
  		default:
  			usage();
***************
*** 106,111 ****
--- 111,122 ----
  	}
  					/* ln target1 target2 directory */
  	sourcedir = argv[argc - 1];
+ 	if (lflag && lstat(sourcedir, &sb) == 0 && S_ISLNK(sb.st_mode)) {
+ 		/* we were asked not to follow symlinks, but found one at
+ 		   the target--simulate "not a directory" error */
+ 		errno = ENOTDIR;
+ 		err(1, "%s", sourcedir);
+ 	}
  	if (stat(sourcedir, &sb))
  		err(1, "%s", sourcedir);
  	if (!S_ISDIR(sb.st_mode))
***************
*** 136,143 ****
  		}
  	}
  
! 	/* If the source is a directory, append the target's name. */
! 	if (isdir || !stat(source, &sb) && S_ISDIR(sb.st_mode)) {
  		if ((p = strrchr(target, '/')) == NULL)
  			p = target;
  		else
--- 147,157 ----
  		}
  	}
  
! 	/* If the source is a directory (and not a symlink if lflag),
! 	   append the target's name. */
! 	if (isdir ||
! 	    !lstat(source, &sb) && S_ISDIR(sb.st_mode) ||
! 	    !lflag && !stat(source, &sb) && S_ISDIR(sb.st_mode)) {
  		if ((p = strrchr(target, '/')) == NULL)
  			p = target;
  		else
***************
*** 164,169 ****
  {
  
  	(void)fprintf(stderr,
! 	    "usage:\tln [-fs] file1 file2\n\tln [-fs] file ... directory\n");
  	exit(1);
  }
--- 178,183 ----
  {
  
  	(void)fprintf(stderr,
! 	    "usage:\tln [-fsh] file1 file2\n\tln [-fsh] file ... directory\n");
  	exit(1);
  }
>Audit-Trail:
>Unformatted: