Ruby, $HOME, and getpwnam
I would expect Dir.home to change when I change HOME environment variable in Ruby:
$ echo $HOME
/home/efranz
$ pwd
/home/efranz
$ ls -ld ~
drwx------ 2 efranz PZS0714 4096 Oct 31 14:54 /home/efranz
$ HOME=$(mktemp -d)
$ echo $HOME
/tmp/tmp.IkjRJV5IYi
$ pwd
/home/efranz
$ ls -ld ~
drwx------ 2 efranz PZS0714 4096 Oct 31 14:57 /tmp/tmp.IkjRJV5IYi
$ (ls -ld ~)
drwx------ 2 efranz PZS0714 4096 Oct 31 14:57 /tmp/tmp.IkjRJV5IYi
$ ruby -e "puts \`ls -ld ~\`"
drwx------ 2 efranz PZS0714 4096 Oct 31 14:57 /tmp/tmp.IkjRJV5IYi
$ ruby -e "puts Dir.home"
/tmp/tmp.IkjRJV5IYi
It does. But not for Dir.home(USERNAME) or File.expand_path:
$ ruby -e "puts Dir.home('efranz')"
/home/efranz
$ ruby -e "puts File.expand_path '~efranz'"
/home/efranz
What is happening? Lets peek at the implementation of Dir.home. (I’m using system Ruby ruby 2.0.0p648 on RHEL7 for this post)
https://ruby-doc.org/core-2.0.0/Dir.html#method-c-home
return rb_home_dir(u, rb_str_new(0, 0));
Returns the home directory of the current user or the named user if given.
This is one function that executes two different code paths based on the argument provided (or omitted). See https://www.ruby-lang.org/en/news/2015/12/16/ruby-2-0-0-p648-released/ It is actually stored in subversion, but here is a copy in GitHub.
- If
rb_home_diris called without the username, it returns the value ofgetenv("HOME"): - If
rb_home_diris called with a username, it gets the password struct viagetpwnam(user)
So this can cause problems if you try to change the home directory for the Ruby process you are executing by modifying the HOME environment variable and you are using RubyGems <v3 because RubyGems has an internal method find_home that uses File.expand_path "~" instead of Dir.home. See https://github.com/rubygems/rubygems/commit/47c9f378687a6f806561a14dd50eae8eb4652882 which switched from using File.expand_path "~" to Dir.home to find the home directory.
See man 3 getpwnam
The getpwnam() function returns a pointer to a structure containing the broken-out fields of the record in the password database (e.g., the local password file /etc/passwd, NIS, and LDAP) that matches the username name.
Conclusion
Be careful to consider whether or not it is appropriate to use Dir.home or Dir.home(user) and treat them as two separate functions. And assume that File.expand_path (or basically anything that resolves ~ in Ruby) is going to use getpwnam.
rubygems is now careful about this as of v3
See https://github.com/rubygems/rubygems/commit/47c9f378687a6f806561a14dd50eae8eb4652882 which switched from using File.expand_path "~" to Dir.home to find the home directory.