Skip to content

Commit e713232

Browse files
authored
fix: resolve symbolic links through IO.FS.realPath on windows (#8534)
This PR fixes `IO.FS.realPath` on windows to take symbolic links into account. Closes #810
1 parent 0d0da76 commit e713232

File tree

5 files changed

+50
-35
lines changed

5 files changed

+50
-35
lines changed

src/runtime/io.cpp

Lines changed: 22 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -958,17 +958,35 @@ extern "C" LEAN_EXPORT obj_res lean_io_realpath(obj_arg fname, obj_arg) {
958958
#if defined(LEAN_WINDOWS)
959959
constexpr unsigned BufferSize = 8192;
960960
char buffer[BufferSize];
961-
DWORD retval = GetFullPathName(string_cstr(fname), BufferSize, buffer, nullptr);
961+
HANDLE handle = CreateFile(string_cstr(fname), 0, FILE_SHARE_READ, NULL, OPEN_EXISTING, FILE_FLAG_BACKUP_SEMANTICS, NULL);
962+
if (handle == INVALID_HANDLE_VALUE) {
963+
obj_res res = mk_file_not_found_error(fname);
964+
dec_ref(fname);
965+
return res;
966+
}
967+
DWORD retval = GetFinalPathNameByHandle(handle, buffer, BufferSize, 0);
968+
CloseHandle(handle);
962969
if (retval == 0 || retval > BufferSize) {
963970
return io_result_mk_ok(fname);
964971
} else {
965972
dec_ref(fname);
973+
char * res = buffer;
974+
if (memcmp(res, "\\\\?\\", 4) == 0) {
975+
if (memcmp(res + 4, "UNC\\", 4) == 0) {
976+
// network path: convert "\\\\?\\UNC\\..." to "\\\\..."
977+
res[6] = '\\';
978+
res += 6;
979+
} else {
980+
// simple path: convert "\\\\?\\C:\\.." to "C:\\..."
981+
res += 4;
982+
}
983+
}
966984
// Hack for making sure disk is lower case
967985
// TODO(Leo): more robust solution
968-
if (strlen(buffer) >= 2 && buffer[1] == ':') {
969-
buffer[0] = tolower(buffer[0]);
986+
if (strlen(res) >= 2 && res[1] == ':') {
987+
res[0] = tolower(res[0]);
970988
}
971-
return io_result_mk_ok(mk_string(buffer));
989+
return io_result_mk_ok(mk_string(res));
972990
}
973991
#else
974992
char buffer[PATH_MAX];

src/util/path.cpp

Lines changed: 0 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -233,29 +233,4 @@ std::vector<std::string> read_dir(std::string const &dirname) {
233233
#endif
234234
return files;
235235
}
236-
237-
std::string lrealpath(std::string const & fname) {
238-
#if defined(LEAN_EMSCRIPTEN)
239-
return fname;
240-
#elif defined(LEAN_WINDOWS)
241-
constexpr unsigned BufferSize = 8192;
242-
char buffer[BufferSize];
243-
DWORD retval = GetFullPathName(fname.c_str(), BufferSize, buffer, nullptr);
244-
if (retval == 0 || retval > BufferSize) {
245-
return fname;
246-
} else {
247-
return std::string(buffer);
248-
}
249-
#else
250-
constexpr unsigned BufferSize = 8192;
251-
char buffer[BufferSize];
252-
char * tmp = realpath(fname.c_str(), buffer);
253-
if (tmp) {
254-
std::string r(tmp);
255-
return r;
256-
} else {
257-
throw file_not_found_exception(fname);
258-
}
259-
#endif
260-
}
261236
}

src/util/path.h

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,4 @@ optional<bool> is_dir(std::string const & fn);
4444
std::vector<std::string> read_dir(std::string const & dirname);
4545

4646
time_t get_mtime(std::string const & fname);
47-
48-
LEAN_EXPORT std::string lrealpath(std::string const & fname);
4947
}

tests/lean/run/parsePrelude.lean

Lines changed: 1 addition & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,6 @@
11
import Lean.Parser
22

3-
def test : IO Unit :=
4-
if System.Platform.isWindows then
5-
pure () -- TODO investigate why the following doesn't work on Windows
6-
else do
3+
def test : IO Unit := do
74
let env ← Lean.mkEmptyEnvironment;
85
discard <| Lean.Parser.testParseFile env (System.mkFilePath ["..", "..", "..", "src", "Init", "Prelude.lean"]);
96
IO.println "done"

tests/lean/run/realPath.lean

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
/-!
2+
Tests that the `IO.FS.realPath` function on windows resolves links.
3+
-/
4+
5+
/-
6+
Note: The test below circumvents #8547 by misusing quotation marks in order to run `mklink`.
7+
This test will need to be updated once that issue is fixed.
8+
-/
9+
10+
def realPathTest : IO Unit := do
11+
unless System.Platform.isWindows do
12+
return
13+
let dir ← IO.currentDir
14+
let tmpDir := dir / "tmp_realpath_test_dir"
15+
let tmpJunct := dir / "tmp_realpath_test_junction"
16+
IO.FS.createDir (dir / tmpDir)
17+
let cmd := "cmd.exe\" /c mklink /j \"" ++ tmpJunct.toString ++ "\" \"" ++ tmpDir.toString
18+
discard <| IO.Process.run { cmd }
19+
let realPath1 ← IO.FS.realPath tmpDir
20+
let realPath2 ← IO.FS.realPath tmpJunct
21+
IO.FS.removeDir tmpJunct
22+
IO.FS.removeDir tmpDir
23+
IO.println s!"{realPath1} vs {realPath2}"
24+
if realPath1 != realPath2 then
25+
throw (.userError ("mismatch " ++ realPath1.toString ++ " with " ++ realPath2.toString))
26+
27+
#eval realPathTest

0 commit comments

Comments
 (0)