Since commit 5454df9c31 ("rename: check source file access early")
rename fails early for symlinks with non-existing target (regression),
because access() dereferences the link.
From access(2):
"access() checks whether the calling process can access the file pathname.
If pathname is a symbolic link, it is dereferenced."
Thus replace access() with faccessat() and lstat() as fallback,
(as in do_symlink()), that is equivalent for symlink and files.
From fsaccess(2) and stat(2):
"The faccessat() system call operates in exactly the same way as access(),
except for the differences described here.
[...]
If pathname is relative and dirfd is the special value AT_FDCWD, then pathname
is interpreted relative to the current working directory of the calling process
(like access()).
[...]
AT_SYMLINK_NOFOLLOW
If pathname is a symbolic link, do not dereference it:
instead return information about the link itself."
"lstat() is identical to stat(), except that if pathname is a symbolic link, then
it returns information about the link itself, not the file that it refers to."
Testing
-------
1) symlink with existing target
2) symlink with non-existing target
3) non-existing symlink
4) existing file
5) non-existing file
Before:
$ touch file-found
$ ln -s file-found symlink-1
$ ./rename sym symbolic- symlink-1 # XPASS.
$ echo $?
0
$ ln -s file-not-found symlink-2
$ ./rename sym symbolic- symlink-2 # FAIL! REGRESSION.
rename: symlink-2: not accessible: No such file or directory
$ echo $?
1
$ ./rename sym symbolic- symlink-3 # XFAIL.
rename: symlink-3: not accessible: No such file or directory
$ echo $?
1
$ touch file-found
$ ./rename found existing file-found # XPASS.
$ echo $?
0
$ ./rename found existing file-not-found # XFAIL.
rename: file-not-found: not accessible: No such file or directory
$ echo $?
1
After:
$ touch file-found
$ ln -s file-found symlink-1
$ ./rename sym symbolic- symlink-1 # XPASS.
$ echo $?
0
$ ln -s file-not-found symlink-2
$ ./rename sym symbolic- symlink-2 # PASS! REGRESSION FIXED.
$ echo $?
0
$ ./rename sym symbolic- symlink-3 # XFAIL.
rename: symlink-3: not accessible: No such file or directory
$ echo $?
1
$ touch file-found
$ ./rename found existing file-found # XPASS.
$ echo $?
0
$ ./rename found existing file-not-found # XFAIL.
rename: file-not-found: not accessible: No such file or directory
$ echo $?
1
And to test/simulate faccessat()'s EINVAL for AT_SYMLINK_NOFOLLOW
for Mac OS X, per commit 826538bf64 ("rename: skip faccessat()
failure if AT_SYMLINK_NOFOLLOW is not a valid flag"), forced 'if'
to evaluate to false so that lstat() is taken.
It still fails early; the error messages are slightly different
('not accessible' vs. 'stat of ... failed') but still tell same
'No such file or directory'; exit code is the same as well.
$ ./rename sym symbolic- symlink-3 # XFAIL. DIFF MSG/SAME RC.
rename: stat of symlink-3 failed: No such file or directory
$ echo $?
1
$ ./rename found existing file-not-found # XFAIL. DIFF MSG/SAME RC.
rename: stat of file-not-found failed: No such file or directory
$ echo $?
1
Tested on commit 2b41c409e ("Merge branch 'blkd-err' of ...")
Signed-off-by: Mauricio Faria de Oliveira <mfo@canonical.com>