Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Resolve symlinks when symlink points to file in same directory #689

Closed
wants to merge 1 commit into from
Closed

Resolve symlinks when symlink points to file in same directory #689

wants to merge 1 commit into from

Conversation

mdippery
Copy link

@mdippery mdippery commented Sep 6, 2016

I encountered a bug in abs_dirname when using pyenv. I had a file structure that looked like this:

/dept/is/dev/mdippery/opt/bin/asciidoc -> ../Cellar/asciidoc/bin/asciidoc
/dept/is/dev/mdippery/opt/Cellar/asciidoc/bin/asciidoc -> asciidoc.py
/dept/is/dev/mdippery/opt/Cellar/asciidoc/bin/asciidoc.py

pyenv's abs_dirname was encountering an error when it tried to resolve this system of symlinks. When abs_dirname /dept/is/dev/mdippery/opt/bin/asciidoc called, the following steps would happen:

  1. $path was set to /dept/is/dev/mdippery/opt/bin/asciidoc.
  2. Because [ -n $path ] was true, pyenv would enter the for loop on Line 42 of ~/.pyenv/libexec/pyenv.
  3. $parent would be set to /dept/is/dev/mdippery/opt/bin.
  4. pyenv would cd to $parent.
  5. resolve_link asciidoc would be called, and return ../Cellar/asciidoc/bin/asciidoc, which would be set to $path.
  6. The for loop would start again, since [ -n $path ] is still true.
  7. $parent would be set to ../Cellar/asciidoc/bin.
  8. pyenv would cd to $parent.
  9. resolve_link asciidoc would be called, and return asciidoc.py, which would be set to $path.
  10. The for loop would start again, since [ -n $path ] is still true.
  11. Here's the bug: $parent would be set to asciidoc.py, since that is what ${path%/*} returns when $path is simply asciidoc.py, with no parent information.
  12. pyenv would naïvely assume that is a directory, and attempt to cd to it. This causes an error to be thrown.

The simplest solution seems to be to check whether the "basename" of a path is the same as its "parent" (i.e., there is a symlink that points to another file in the same directory), and break from the loop if that is the case.

This fixes a bug that is referenced by #580, #588, and #607. A fix was also supplied in #502, but was never merged. (This fix is a bit different and may work better.)

@mdippery
Copy link
Author

mdippery commented Sep 6, 2016

Almost exactly the same bug is described in rbenv#868 as well.

@yyuu
Copy link
Contributor

yyuu commented Sep 6, 2016

Could you update the changes as exactly same as rbenv#868 to avoid conflict?

@mdippery
Copy link
Author

mdippery commented Sep 6, 2016

I'm not sure that rbenv#868 actually works properly, since it won't actually break out of the loop.

@mdippery
Copy link
Author

mdippery commented Sep 6, 2016

Actually, I take that back. I can update it so it looks the same as rbenv#868, except that rbenv#868 was never merged into rbenv, either, so there's not actually a conflict.

I encountered a bug in `abs_dirname` when using pyenv. I had a file
structure that looked like this:

    /dept/is/dev/mdippery/opt/bin/asciidoc -> ../Cellar/asciidoc/bin/asciidoc
    /dept/is/dev/mdippery/opt/Cellar/asciidoc/bin/asciidoc -> asciidoc.py
    /dept/is/dev/mdippery/opt/Cellar/asciidoc/bin/asciidoc.py

pyenv's `abs_dirname` was encountering an error when it tried to resolve
this system of symlinks. When `abs_dirname
/dept/is/dev/mdippery/opt/bin/asciidoc` called, the following steps
would happen:

   1. `$path` was set to `/dept/is/dev/mdippery/opt/bin/asciidoc`.
   2. Because `[ -n $path ]` was true, pyenv would enter the for loop on
      Line 42 of `~/.pyenv/libexec/pyenv`.
   3. `$parent` would be set to `/dept/is/dev/mdippery/opt/bin`.
   4. pyenv would `cd` to `$parent`.
   5. `resolve_link asciidoc` would be called, and return
      `../Cellar/asciidoc/bin/asciidoc`, which would be set to `$path`.
   6. The for loop would start again, since `[ -n $path ]` is still true.
   7. `$parent` would be set to `../Cellar/asciidoc/bin`.
   8. pyenv would `cd` to `$parent`.
   9. `resolve_link asciidoc` would be called, and return `asciidoc.py`,
      which would be set to `$path`.
  10. The for loop would start again, since `[ -n $path ]` is still true.
  11. _Here's the bug:_ `$parent` would be set to `asciidoc.py`, since
      that is what `${path%/*}` returns when `$path` is simply
      `asciidoc.py`, with no parent information.
  12. pyenv would naïvely assume that is a directory, and attempt to
      `cd` to it. _This causes an error to be thrown._

The simplest solution seems to be to check whether the "basename" of a
path is the same as its "parent" (i.e., there is a symlink that points
to another file in the same directory), and break from the loop if that
is the case.
@mikegleasonjr
Copy link

I have this wrong output also when running ansible from source. ansible-playbook is an alias of ansible in the same directory.

@blueyed
Copy link
Contributor

blueyed commented Jun 23, 2017

Yeah, came across my old PR (rbenv/rbenv#868) for this again.
Seems to be copied code from bats after all: sstephenson/bats#224.
There are even more places where this is used as a copy in pyenv.. :/

cd "${path%/*}"
local parent="${path%/*}"
if [ "$path" = "$parent" ]; then break; fi
cd "$parent"
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Breaking is wrong here: the local file might be a symlink itself that needs to get resolved.

blueyed added a commit to blueyed/rbenv that referenced this pull request Jun 24, 2017
Without this patch you would see the following error for a symlink like
"/usr/bin/asciidoc -> asciidoc.py":

> …/libexec/{py,rb}env: line 43: cd: asciidoc.py: Not a directory

Ref: sstephenson/bats#224
Ref: pyenv/pyenv#689
Closes: rbenv#868
@blueyed
Copy link
Contributor

blueyed commented Jun 24, 2017

A PR with a test is ready in rbenv itself now: rbenv/rbenv#868 (comment).

@blueyed
Copy link
Contributor

blueyed commented Jun 24, 2017

btw: a workaround, which also improves performance is to run make in pyenv's src dir, which will generate libexec/pyenv-realpath.dylib to skip this method altogether.

@jonganc
Copy link

jonganc commented Sep 19, 2017

How about simply changing cd "${path%/*}" to [[ "$path" = */* ]] && cd "${path%/*} (i.e. only change directory if path is not in the current directory)?
I was looking at @blueyed's original commit. His rbenv suggestion works fine.

@blueyed
Copy link
Contributor

blueyed commented Sep 21, 2017

Yeah, I have to pick that up again.
Maybe somebody can help over there?

@jonganc
Copy link

jonganc commented Sep 21, 2017

If I understood the rbenv discussion, mislav didn't think this was a relevant use case. On the pyenv side at least, I should point out that the bug shows up in the last few releases of ansible (i.e. note that in the bin directory, most executables are symlinks to ansible), so it's not some obscure issue.

@blueyed
Copy link
Contributor

blueyed commented Sep 19, 2018

Closing in favor of #1216.

@blueyed blueyed closed this Sep 19, 2018
@blueyed
Copy link
Contributor

blueyed commented Sep 19, 2018

most executables are symlinks to ansible

btw: that does not seem to be the case anymore, right?

It happens for pyenv because pyenv uses abs_dirname for the file argument, which rbenv does not.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet

5 participants