Bug 191974

Summary: ln(1): |ln -sF| never calls rmdir(2) on target_file == target_dir, only target_dir/basename
Product: Base System Reporter: Jan Beich <jbeich>
Component: binAssignee: freebsd-bugs (Nobody) <bugs>
Status: New ---    
Severity: Affects Only Me CC: bycn82, glebius, joseph_stewart, ports
Priority: ---    
Version: CURRENT   
Hardware: Any   
OS: Any   
Bug Depends on:    
Bug Blocks: 92149    
Description Flags
improve manpage
improve manpage, v1.1 none

Description Jan Beich freebsd_committer 2014-07-19 23:46:36 UTC
According to the manpage -F option is supposed to make ln(1) remove
target_file which happens to be a directory. What actually happens is
target_file converted to target_dir/basename and always fails when tested
with S_ISDIR().

It's also unclear why -s flag is required. If the source_file is not
a directory ln(1) can just call rmdir(2) + link(2) instead of rmdir(2) + symlink(2).

$ echo >foo
$ mkdir bar
$ ln -sF foo bar
$ ls bar

but replacing children works fine

$ echo >foo
$ mkdir -p bar/foo
$ ln -sF foo bar
$ stat -x bar/foo | head -2
  File: "bar/foo"
  Size: 3            FileType: Symbolic Link
Comment 1 bycn82 2014-07-20 06:51:04 UTC
I checked the source code of the ln, I agree with him, something wrong there! 

the main logic are here.
	switch(argc) {
	case 0:
	case 1:				/* ln source */
		exit(linkit(argv[0], ".", 1));
	case 2:				/* ln source target */
		exit(linkit(argv[0], argv[1], 0));
        /* ln source1 source2 directory */

I did the below test with my explanation.

root@FBHead:~ # uname -a
FreeBSD FBHead 11.0-CURRENT FreeBSD 11.0-CURRENT #0 r268881: Sun Jul 20 01:32:21 UTC 2014     root@FBHead:/usr/obj/usr/src/sys/GENERIC  amd64

root@FBHead:~ # ln -s -F
usage: ln [-s [-F] | -L | -P] [-f | -i] [-hnv] source_file [target_file]
       ln [-s [-F] | -L | -P] [-f | -i] [-hnv] source_file ... target_dir
       link source_file target_file
the argc==0, so show the usage.

root@FBHead:~ # ln -s -F foo
root@FBHead:~ # ls -al foo
lrwxr-xr-x  1 root  wheel  3 Jul 20 04:42 foo -> foo

according to the logic, the argc==1, so it will call linkit(argv[0], ".", 1) so the result will be foo->foo, it will be useless, because it will delete the current foo and create the new foo soft link with link to itself!!!

root@FBHead:~ # rm foo
root@FBHead:~ # touch foo
root@FBHead:~ # mkdir bar
root@FBHead:~ # ln -s -F foo bar
root@FBHead:~ # ls -al bar/foo 
lrwxr-xr-x  1 root  wheel  3 Jul 20 04:47 bar/foo -> foo
here the argc==2, so it will call linkit(argv[0], argv[1], 0)
and the 3rd parameter "0" means the argv[1] is not a directory!!
anyway, the result is not correct, 

root@FBHead:~ # ln -s -F foo1 foo2 bar
in this case, the argc is not 0 1 2 , so it will be go to default: and continue, actually it will just loop the parameters and call linkit, the result will be same as previous one.

all this are not make sense!!! the -F in the man page also bullshit! I think all this features are the same on other systems for example Linux or other BSD systems. because the code are not new created. already there for long long !!!

anyway, I dont recommend to make changes in the source code immediately, but update the document first. make it clear about how it works. what do you guys think ?
Comment 2 bycn82 2014-07-20 07:02:41 UTC
OK, I was wrong, 
I just got below from my CentOS.

#touch foo1
#ls -s -f foo1
ln: `foo1' and `./foo1' are the same file

So the conclusion is the "ln" command on CentOS is smarter than the one in FreeBSD.
Comment 3 Eugene Grosbein 2014-07-21 08:20:32 UTC
The flag -F was introduced solely for creating symlinks for directories. Hard links for a directory is not permitted in FreeBSD, so -s flag is needed (otherwise, -F is ignored).

One should not use -F for non-directory source, it's meaningless indeed.
Comment 4 Jan Beich freebsd_committer 2014-07-21 11:40:07 UTC
Created attachment 144837 [details]
improve manpage

-f flag can replace a file with either symlink to file or directory and hardlink to file. -f already errors out if source_file is a directory and -F inherits that. But comment 3 still doesn't explain *why* -F is artifically limited to symlinks and only directories or behavior inconsistency if target_file is a directory.

hadlink case

  $ echo >foo
  $ echo >bar
  $ ln -F foo bar


  $ echo >foo
  $ mkdir bar
  $ ln -F foo bar

symlink case

  $ echo >foo
  $ echo >bar
  $ ln -sF foo bar

  $ mkdir foo
  $ echo >bar
  $ ln -sF foo bar


  $ mkdir foo
  $ mkdir bar
  $ ln -sF foo bar

  $ echo >foo
  $ mkdir bar
  $ ln -sF foo bar

Let's remove some ambiguity in manpage such as when target file is a directory. Given current behavior its notion doesn't change from target directory and rmdir(2) is only called under it but not on the directory itself.

Also, -F is not only an extension but incompatible with GNU ln.
Comment 5 Jan Beich freebsd_committer 2014-07-21 12:13:08 UTC
Created attachment 144841 [details]
improve manpage, v1.1

typos and drop basename(3) reference
Comment 6 Gleb Smirnoff freebsd_committer 2014-07-27 05:58:01 UTC
Option isn't specific to FreeBSD. Mac OS X also supports it.