Skip to content

Commit

Permalink
Let Dir.current respect $PWD (#12471)
Browse files Browse the repository at this point in the history
This patch enhances the implementation of `Dir.current` on POSIX platforms to respects the environment variable `PWD`.
The variable holds the path to the current working directory as entered by the user. This is in accordance with the `pwd` utility. There's a validation in place to check that the value actually points to the current working directory.
Without this change, `Dir.current` would typically return the path with any symbolic links resolved and thus disregarding that the user might be in a different path via symbolic links.
The change effectively implements the GNU extension `get_current_dir_name` (which is not portable) whereas the previous behaviour was equivalent to the standard `getcwd`. In fact, it is very similar to the [implementation of `get_current_dir_name` in musl](https://git.musl-libc.org/cgit/musl/tree/src/misc/get_current_dir_name.c?id=37e18b7bf307fa4a8c745feebfcba54a0ba74f30).

Windows is not affected. The environment variable `PWD` is specific to POSIX. The Window API function `GetCurrentDirectoryW` already returns the path to the current directory as entered by the user and symbolic links are not resolved.

The previous behaviour of `Dir.current` is equivalent to `File.real_path(Dir.current)`.

`Dir.cd` is unaffected by this change. It does *not* set `$PWD` to the changed directory path.
  • Loading branch information
straight-shoota authored Sep 15, 2022
1 parent cfa0494 commit bab729a
Show file tree
Hide file tree
Showing 3 changed files with 51 additions and 3 deletions.
38 changes: 36 additions & 2 deletions spec/std/dir_spec.cr
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
require "./spec_helper"
require "../support/env"

private def unset_tempdir
{% if flag?(:windows) %}
Expand Down Expand Up @@ -507,8 +508,41 @@ describe "Dir" do
end
end

it ".current" do
Dir.current.should eq(`#{{{ flag?(:win32) ? "cmd /c cd" : "pwd" }}}`.chomp)
describe ".current" do
it "matches shell" do
Dir.current.should eq(`#{{{ flag?(:win32) ? "cmd /c cd" : "pwd" }}}`.chomp)
end

# Skip spec on Windows due to weak support for symlinks and $PWD.
{% unless flag?(:win32) %}
it "follows $PWD" do
with_tempfile "current-pwd" do |path|
Dir.mkdir_p path
# Resolve any symbolic links in path caused by tmpdir being a link.
# For example on macOS, /tmp is a symlink to /private/tmp.
path = File.real_path(path)

target_path = File.join(path, "target")
link_path = File.join(path, "link")
Dir.mkdir_p target_path
File.symlink(target_path, link_path)

Dir.cd(link_path) do
with_env({"PWD" => nil}) do
Dir.current.should eq target_path
end

with_env({"PWD" => link_path}) do
Dir.current.should eq link_path
end

with_env({"PWD" => "/some/other/path"}) do
Dir.current.should eq target_path
end
end
end
end
{% end %}
end

describe ".tempdir" do
Expand Down
9 changes: 9 additions & 0 deletions src/crystal/system/unix/dir.cr
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,15 @@ module Crystal::System::Dir
end

def self.current : String
# If $PWD is set and it matches the current path, use that.
# This helps telling apart symlinked paths.
if (pwd = ENV["PWD"]?) && pwd.starts_with?("/") &&
(pwd_info = ::Crystal::System::File.info?(pwd, follow_symlinks: true)) &&
(dot_info = ::Crystal::System::File.info?(".", follow_symlinks: true)) &&
pwd_info.same_file?(dot_info)
return pwd
end

unless dir = LibC.getcwd(nil, 0)
raise ::File::Error.from_errno("Error getting current directory", file: "./")
end
Expand Down
7 changes: 6 additions & 1 deletion src/dir.cr
Original file line number Diff line number Diff line change
Expand Up @@ -176,7 +176,12 @@ class Dir
@closed = true
end

# Returns the current working directory.
# Returns an absolute path to the current working directory.
#
# The result is similar to the shell commands `pwd` (POSIX) and `cd` (Windows).
#
# On POSIX systems, it respects the environment value `$PWD` if available and
# if it points to the current working directory.
def self.current : String
Crystal::System::Dir.current
end
Expand Down

0 comments on commit bab729a

Please sign in to comment.