]> git.ipfire.org Git - thirdparty/util-linux.git/commit
rename: fix regression for symlink with non-existing target
authorMauricio Faria de Oliveira <mfo@canonical.com>
Tue, 7 Jul 2020 18:49:13 +0000 (15:49 -0300)
committerMauricio Faria de Oliveira <mfo@canonical.com>
Tue, 7 Jul 2020 19:19:15 +0000 (16:19 -0300)
commit477239ce0d60384642b51c950688136fdefec815
tree7e766c6d67df9bce39bf29885605b2053844fe36
parent2b41c409e7616b6e07bb47eaee90b82d7fdc120c
rename: fix regression for symlink with non-existing target

Since commit 5454df9c3110 ("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 826538bf64c5 ("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>
misc-utils/rename.c