From 20c7cf900a209a3cf9200130651deecaef192133 Mon Sep 17 00:00:00 2001 From: Benoit Daloze Date: Mon, 14 Nov 2022 19:17:31 +0100 Subject: [PATCH] Resolve the current user home even when $HOME is not set * Fixes https://github.com/oracle/truffleruby/issues/2784 * Use getpwuid_r(getuid()) for simplicity and reliability. --- CHANGELOG.md | 1 + mx.truffleruby/native | 2 +- spec/ruby/core/dir/home_spec.rb | 16 +++++++++ src/main/c/truffleposix/truffleposix.c | 46 +++++++++++++++++++++++++ src/main/ruby/truffleruby/core/dir.rb | 25 +++++++++++--- src/main/ruby/truffleruby/core/posix.rb | 1 + 6 files changed, 85 insertions(+), 6 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 1133d102472d..52e207725af9 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -39,6 +39,7 @@ Compatibility: * Support `offset` keyword argument for `String#unpack` and `String#unpack1` (@andrykonchin). * Fix `Process.detach` and cast `pid` argument to `Integer` (#2782, @andrykonchin). * `rb_to_id()` should create a static `ID`, used by RMagick (@eregon). +* Resolve the current user home even when `$HOME` is not set (#2784, @eregon) Performance: diff --git a/mx.truffleruby/native b/mx.truffleruby/native index b07d294e9f62..c0669ea9cfb4 100644 --- a/mx.truffleruby/native +++ b/mx.truffleruby/native @@ -1,6 +1,6 @@ GRAALVM_SKIP_ARCHIVE=true DYNAMIC_IMPORTS=/tools,/compiler,/substratevm -COMPONENTS=TruffleRuby,suite:tools,GraalVM compiler,SubstrateVM +COMPONENTS=TruffleRuby,suite:tools,GraalVM compiler,SubstrateVM,Native Image NATIVE_IMAGES=suite:sulong,lib:rubyvm # To also create the standalone DISABLE_INSTALLABLES=false diff --git a/spec/ruby/core/dir/home_spec.rb b/spec/ruby/core/dir/home_spec.rb index 8377f1dc97f7..85875093c455 100644 --- a/spec/ruby/core/dir/home_spec.rb +++ b/spec/ruby/core/dir/home_spec.rb @@ -19,6 +19,18 @@ it "returns a non-frozen string" do Dir.home.should_not.frozen? end + + it "returns a string with the filesystem encoding" do + Dir.home.encoding.should == Encoding.find("filesystem") + end + + platform_is_not :windows do + it "works even if HOME is unset" do + ENV.delete('HOME') + Dir.home.should.start_with?('/') + Dir.home.encoding.should == Encoding.find("filesystem") + end + end end describe "when called with the current user name" do @@ -37,6 +49,10 @@ it "returns a non-frozen string" do Dir.home(ENV['USER']).should_not.frozen? end + + it "returns a string with the filesystem encoding" do + Dir.home(ENV['USER']).encoding.should == Encoding.find("filesystem") + end end it "raises an ArgumentError if the named user doesn't exist" do diff --git a/src/main/c/truffleposix/truffleposix.c b/src/main/c/truffleposix/truffleposix.c index 4b0c463fb5b6..60ebcd273c51 100644 --- a/src/main/c/truffleposix/truffleposix.c +++ b/src/main/c/truffleposix/truffleposix.c @@ -177,6 +177,52 @@ int truffleposix_getrusage(double times[4]) { return 0; } +// Keep in sync with truffleposix_get_user_home() below +char* truffleposix_get_current_user_home(void) { + uid_t uid = getuid(); + + struct passwd entry; + struct passwd *result = NULL; + int ret; + + size_t buffer_size = sysconf(_SC_GETPW_R_SIZE_MAX); + if (buffer_size <= 0) { + buffer_size = 16384; + } + + char *buffer = malloc(buffer_size); + if (buffer == NULL) { + return NULL; + } + +retry: + ret = getpwuid_r(uid, &entry, buffer, buffer_size, &result); + if (result != NULL) { + char *home = strdup(entry.pw_dir); + free(buffer); + return home; + } else if (ret == ERANGE) { + buffer_size *= 2; + free(buffer); + buffer = malloc(buffer_size); + if (buffer == NULL) { + return NULL; + } + goto retry; + } else if (ret == EINTR) { + goto retry; + } else if (ret == EIO || ret == EMFILE || ret == ENFILE) { + free(buffer); + errno = ret; + return NULL; + } else { // result == NULL, which means not found + // ret should be 0 in that case according to the man page, but it doesn't seem to always hold + free(buffer); + return strdup(""); + } +} + +// Keep in sync with truffleposix_get_current_user_home() above char* truffleposix_get_user_home(const char *name) { struct passwd entry; struct passwd *result = NULL; diff --git a/src/main/ruby/truffleruby/core/dir.rb b/src/main/ruby/truffleruby/core/dir.rb index 25b95f356e26..6acaa7e1da24 100644 --- a/src/main/ruby/truffleruby/core/dir.rb +++ b/src/main/ruby/truffleruby/core/dir.rb @@ -209,10 +209,12 @@ def exist?(path) alias_method :exists?, :exist? def home(user = nil) - if user + user = StringValue(user) unless Primitive.nil?(user) + + if user and !user.empty? ptr = Truffle::POSIX.truffleposix_get_user_home(user) if !ptr.null? - home = ptr.read_string + home = ptr.read_string.force_encoding(Encoding.filesystem) Truffle::POSIX.truffleposix_free ptr raise ArgumentError, "user #{user} does not exist" if home.empty? home @@ -220,10 +222,23 @@ def home(user = nil) Errno.handle end else - home = ENV['HOME'] - raise ArgumentError, "couldn't find HOME environment variable when expanding '~'" if Primitive.nil? home + home = ENV['HOME'].dup + if home + home = home.dup.force_encoding(Encoding.filesystem) + else + ptr = Truffle::POSIX.truffleposix_get_current_user_home + if !ptr.null? + home = ptr.read_string.force_encoding(Encoding.filesystem) + Truffle::POSIX.truffleposix_free ptr + raise ArgumentError, "couldn't find home for uid `#{Process.uid}'" if home.empty? + home + else + Errno.handle + end + end + raise ArgumentError, 'non-absolute home' unless home.start_with?('/') - home.dup + home end end diff --git a/src/main/ruby/truffleruby/core/posix.rb b/src/main/ruby/truffleruby/core/posix.rb index 57f398691645..10a2c71472a4 100644 --- a/src/main/ruby/truffleruby/core/posix.rb +++ b/src/main/ruby/truffleruby/core/posix.rb @@ -301,6 +301,7 @@ def self.unsetenv(name) # Other routines attach_function :crypt, [:string, :string], :string, LIBCRYPT + attach_function :truffleposix_get_current_user_home, [], :pointer, LIBTRUFFLEPOSIX attach_function :truffleposix_get_user_home, [:string], :pointer, LIBTRUFFLEPOSIX attach_function :truffleposix_free, [:pointer], :void, LIBTRUFFLEPOSIX