Bug 92149 - [patch] ln(1): ln -f -s does not remove existing directory
Summary: [patch] ln(1): ln -f -s does not remove existing directory
Status: Closed FIXED
Alias: None
Product: Base System
Classification: Unclassified
Component: bin (show other bugs)
Version: 6.0-STABLE
Hardware: Any Any
: Normal Affects Only Me
Assignee: Gleb Smirnoff
URL:
Keywords:
Depends on: 191974
Blocks:
  Show dependency treegraph
 
Reported: 2006-01-22 13:50 UTC by Eugene Grosbein
Modified: 2014-07-19 23:46 UTC (History)
0 users

See Also:


Attachments
file.diff (1.18 KB, patch)
2006-01-22 13:50 UTC, Eugene Grosbein
no flags Details | Diff

Note You need to log in before you can comment on or make changes to this bug.
Description Eugene Grosbein 2006-01-22 13:50:02 UTC
	"ln -f -s" may be used to create a symlink to the file and
	the target file will be unlinked if it exists.

	However, ln will fail with 'Operation not permitted' message
	when target is a directory because unlink(2) does not remove
	empty directories.

Fix: The following patch allows ln(1) to remove existing target
	if the -s option is supplied and the target is the (empty) directory.

	The patch is for src/bin/ln:
How-To-Repeat: 
	mkdir -p dir/etc
	ln -f -s /etc dir
Comment 1 Eugene Grosbein 2006-01-23 06:08:07 UTC
Hi!

Sorry, I've sent wrong (buggy) version of patch, here is right one.

--- ln.c.orig	Sun Jan 22 20:05:44 2006
+++ ln.c	Mon Jan 23 12:47:00 2006
@@ -198,9 +198,17 @@
 	/*
 	 * If the file exists, then unlink it forcibly if -f was specified
 	 * and interactively if -i was specified.
+	 *
+	 * For the directory, remove it when dealing with symbolic link only.
 	 */
 	if (fflag && exists) {
-		if (unlink(source)) {
+		if (S_ISDIR(sb.st_mode)) {
+			if (sflag && rmdir(source)) {
+				warn("%s", source);
+				return (1);
+			}
+		}
+		else if (unlink(source)) {
 			warn("%s", source);
 			return (1);
 		}
@@ -216,7 +224,13 @@
 			return (1);
 		}
 
-		if (unlink(source)) {
+		if (S_ISDIR(sb.st_mode)) {
+			if (sflag && rmdir(source)) {
+				warn("%s", source);
+				return (1);
+			}
+		}
+		else if (unlink(source)) {
 			warn("%s", source);
 			return (1);
 		}
--- ln.1.orig	Sun Jan 22 20:18:12 2006
+++ ln.1	Mon Jan 23 12:44:28 2006
@@ -69,8 +69,12 @@
 The options are as follows:
 .Bl -tag -width flag
 .It Fl f
-If the target file already exists,
-then unlink it so that the link may occur.
+If the target file or (empty) directory already exists,
+then remove it so that the link may occur.
+Note that no attempt to remove the directory is performed
+when running without the
+.Fl s
+option.
 (The
 .Fl f
 option overrides any previous
Comment 2 Gleb Smirnoff freebsd_committer 2006-01-23 08:53:36 UTC
E> >Description:
E> 
E> 	"ln -f -s" may be used to create a symlink to the file and
E> 	the target file will be unlinked if it exists.
E> 
E> 	However, ln will fail with 'Operation not permitted' message
E> 	when target is a directory because unlink(2) does not remove
E> 	empty directories.

I think that the current behavior is standard, while suggested behavior
is going to violate SUSv3. At least I understand the standard this way:

  If the destination path exists:

   1.	If the -f option is not specified, ln shall write a diagnostic message
	to standard error, do nothing more with the current source_file, and go
	on to any remaining source_files.
   2.	Actions shall be performed equivalent to the unlink() function defined in
	the System Interfaces volume of IEEE Std 1003.1-2001, called using
	destination as the path argument. If this fails for any reason, ln shall
	write a diagnostic message to standard error, do nothing more with the
	current source_file, and go on to any remaining source_files.

 http://www.opengroup.org/onlinepubs/000095399/utilities/ln.html

-- 
Totus tuus, Glebius.
GLEBIUS-RIPN GLEB-RIPE
Comment 3 Eugene Grosbein 2006-01-23 11:32:45 UTC
On Mon, Jan 23, 2006 at 11:53:36AM +0300, Gleb Smirnoff wrote:

> E> 	"ln -f -s" may be used to create a symlink to the file and
> E> 	the target file will be unlinked if it exists.
> E> 
> E> 	However, ln will fail with 'Operation not permitted' message
> E> 	when target is a directory because unlink(2) does not remove
> E> 	empty directories.
> 
> I think that the current behavior is standard, while suggested behavior
> is going to violate SUSv3. At least I understand the standard this way:
> 
>   If the destination path exists:
> 
>    1.	If the -f option is not specified, ln shall write a diagnostic message
> 	to standard error, do nothing more with the current source_file, and go
> 	on to any remaining source_files.
>    2.	Actions shall be performed equivalent to the unlink() function defined in
> 	the System Interfaces volume of IEEE Std 1003.1-2001, called using
> 	destination as the path argument. If this fails for any reason, ln shall
> 	write a diagnostic message to standard error, do nothing more with the
> 	current source_file, and go on to any remaining source_files.
> 
>  http://www.opengroup.org/onlinepubs/000095399/utilities/ln.html

Then I'd like to introduce new command line option enabling desired
behavour. Should I correct the patch?

Eugene Grosbein
Comment 4 Gleb Smirnoff freebsd_committer 2006-01-25 12:06:49 UTC
On Mon, Jan 23, 2006 at 11:40:06AM +0000, Eugene Grosbein wrote:
E>  On Mon, Jan 23, 2006 at 11:53:36AM +0300, Gleb Smirnoff wrote:
E>  
E>  > E> 	"ln -f -s" may be used to create a symlink to the file and
E>  > E> 	the target file will be unlinked if it exists.
E>  > E> 
E>  > E> 	However, ln will fail with 'Operation not permitted' message
E>  > E> 	when target is a directory because unlink(2) does not remove
E>  > E> 	empty directories.
E>  > 
E>  > I think that the current behavior is standard, while suggested behavior
E>  > is going to violate SUSv3. At least I understand the standard this way:
E>  > 
E>  >   If the destination path exists:
E>  > 
E>  >    1.	If the -f option is not specified, ln shall write a diagnostic message
E>  > 	to standard error, do nothing more with the current source_file, and go
E>  > 	on to any remaining source_files.
E>  >    2.	Actions shall be performed equivalent to the unlink() function defined in
E>  > 	the System Interfaces volume of IEEE Std 1003.1-2001, called using
E>  > 	destination as the path argument. If this fails for any reason, ln shall
E>  > 	write a diagnostic message to standard error, do nothing more with the
E>  > 	current source_file, and go on to any remaining source_files.
E>  > 
E>  >  http://www.opengroup.org/onlinepubs/000095399/utilities/ln.html
E>  
E>  Then I'd like to introduce new command line option enabling desired
E>  behavour. Should I correct the patch?

Yes, I think this will be acceptable.

-- 
Totus tuus, Glebius.
GLEBIUS-RIPN GLEB-RIPE
Comment 5 Gleb Smirnoff freebsd_committer 2006-01-31 08:34:40 UTC
State Changed
From-To: open->feedback

Awaiting updated patch.
Comment 6 Eugene Grosbein 2006-02-13 18:23:32 UTC
E>>  Then I'd like to introduce new command line option enabling desired
E>>  behavour. Should I correct the patch?
 
> Yes, I think this will be acceptable.

Here it comes. It introduces new option -F that implies -f
and removes empty target directory when dealing with symlinks.

Index: ln.1
===================================================================
RCS file: /home/ncvs/src/bin/ln/ln.1,v
retrieving revision 1.30
diff -u -r1.30 ln.1
--- ln.1	16 Jan 2005 16:41:57 -0000	1.30
+++ ln.1	13 Feb 2006 18:15:21 -0000
@@ -41,11 +41,11 @@
 .Nd make links
 .Sh SYNOPSIS
 .Nm
-.Op Fl fhinsv
+.Op Fl fFhinsv
 .Ar source_file
 .Op Ar target_file
 .Nm
-.Op Fl fhinsv
+.Op Fl fFhinsv
 .Ar source_file ...
 .Ar target_dir
 .Nm link
@@ -76,6 +76,16 @@
 option overrides any previous
 .Fl i
 options.)
+.It Fl F
+This implies
+.Fl f
+option and will remove the target even if it is empty directory and
+.Fl s
+option is supplied also. No attempt to remove the target directory
+is performed when
+.Fl s
+option is omitted. This is most useful with two non-option arguments
+to create a symbolic link to the source directory.
 .It Fl h
 If the
 .Ar target_file
@@ -99,6 +109,8 @@
 .Fl i
 option overrides any previous
 .Fl f
+and
+.Fl F
 options.)
 .It Fl n
 Same as
@@ -179,6 +191,10 @@
 They are provided solely for compatibility with other
 .Nm
 implementations.
+.Pp
+The
+.Fl F
+option is FreeBSD extention and should not be used in portable scripts.
 .Sh SEE ALSO
 .Xr link 2 ,
 .Xr lstat 2 ,
Index: ln.c
===================================================================
RCS file: /home/ncvs/src/bin/ln/ln.c,v
retrieving revision 1.33
diff -u -r1.33 ln.c
--- ln.c	9 Feb 2005 17:37:37 -0000	1.33
+++ ln.c	13 Feb 2006 18:17:56 -0000
@@ -53,6 +53,7 @@
 #include <unistd.h>
 
 int	fflag;				/* Unlink existing files. */
+int	Fflag;				/* Remove existing empty directories also. */
 int	hflag;				/* Check new name for symlink first. */
 int	iflag;				/* Interactive mode. */
 int	sflag;				/* Symbolic, not hard, link. */
@@ -91,8 +92,11 @@
 		exit(linkit(argv[0], argv[1], 0));
 	}
 
-	while ((ch = getopt(argc, argv, "fhinsv")) != -1)
+	while ((ch = getopt(argc, argv, "fFhinsv")) != -1)
 		switch (ch) {
+		case 'F':
+			Fflag = 1;
+			/* FALLTHROUGH */
 		case 'f':
 			fflag = 1;
 			iflag = 0;
@@ -104,6 +108,7 @@
 		case 'i':
 			iflag = 1;
 			fflag = 0;
+			Fflag = 0;
 			break;
 		case 's':
 			sflag = 1;
@@ -121,6 +126,8 @@
 
 	linkf = sflag ? symlink : link;
 	linkch = sflag ? '-' : '=';
+	if (!sflag)
+		Fflag = 0;
 
 	switch(argc) {
 	case 0:
@@ -198,9 +205,17 @@
 	/*
 	 * If the file exists, then unlink it forcibly if -f was specified
 	 * and interactively if -i was specified.
+	 *
+	 * For the directory, remove it only when -F and -s were specified.
 	 */
 	if (fflag && exists) {
-		if (unlink(source)) {
+		if (Fflag && S_ISDIR(sb.st_mode)) {
+			if (rmdir(source)) {
+				warn("%s", source);
+				return (1);
+			}
+		}
+		else if (unlink(source)) {
 			warn("%s", source);
 			return (1);
 		}
@@ -236,8 +251,8 @@
 usage(void)
 {
 	(void)fprintf(stderr, "%s\n%s\n%s\n",
-	    "usage: ln [-fhinsv] source_file [target_file]",
-	    "       ln [-fhinsv] source_file ... target_dir",
+	    "usage: ln [-fFhinsv] source_file [target_file]",
+	    "       ln [-fFhinsv] source_file ... target_dir",
 	    "       link source_file target_file");
 	exit(1);
 }
Comment 7 Gleb Smirnoff freebsd_committer 2006-02-14 11:08:19 UTC
State Changed
From-To: feedback->patched

Committed to HEAD, thanks! 


Comment 8 Gleb Smirnoff freebsd_committer 2006-02-14 11:08:19 UTC
Responsible Changed
From-To: freebsd-bugs->glebius

I'm handling this one.
Comment 9 Gleb Smirnoff freebsd_committer 2006-03-18 21:49:48 UTC
State Changed
From-To: patched->closed

Merged to RELENG_6.