From 46ffa9002693ad468f3c569cf3cb2a891de308ae Mon Sep 17 00:00:00 2001 From: Thomas Munro Date: Sun, 16 Oct 2022 12:07:33 +1300 Subject: [PATCH v2 06/10] Fix lstat() on broken junction points. When using junction points to emulate symlinks on Windows, one edge case was not handled correctly by commit c5cb8f3b: if a junction point is broken (pointing to a non-existent path), we'd report ENOENT. This doesn't break any known use case, but was noticed while testing and is fixed here for completeness. Also add translation ERROR_CANT_RESOLVE_FILENAME -> ENOENT, as that is one of the errors Windows can report depending on format of the broken path. --- src/port/t/001_filesystem.c | 23 +++++++++++++++-------- src/port/win32error.c | 3 +++ src/port/win32stat.c | 27 ++++++++++++++++++++++----- 3 files changed, 40 insertions(+), 13 deletions(-) diff --git a/src/port/t/001_filesystem.c b/src/port/t/001_filesystem.c index 137af164ed..bb1c681366 100644 --- a/src/port/t/001_filesystem.c +++ b/src/port/t/001_filesystem.c @@ -277,6 +277,13 @@ filesystem_metadata_tests(void) PG_EXPECT(S_ISLNK(statbuf.st_mode)); PG_EXPECT_EQ(statbuf.st_size, strlen(path2), "got expected symlink size"); + make_path(path, "broken-symlink"); + make_path(path2, "does-not-exist"); + PG_EXPECT_SYS(symlink(path2, path) == 0, "make a broken symlink"); + PG_EXPECT_SYS(lstat(path, &statbuf) == 0, "lstat broken symlink"); + PG_EXPECT(S_ISLNK(statbuf.st_mode)); + PG_EXPECT_SYS(unlink(path) == 0); + /* Tests for link() and unlink(). */ make_path(path, "does-not-exist-1"); @@ -347,7 +354,7 @@ filesystem_metadata_tests(void) fd = open(path, O_CREAT | O_EXCL | O_RDWR | PG_BINARY, 0777); PG_EXPECT_SYS(fd >= 0, "touch name1.txt"); - PG_EXPECT_SYS(close(fd) == 0); + PG_REQUIRE_SYS(close(fd) == 0); handle = CreateFile(path, GENERIC_WRITE | GENERIC_READ, 0, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL); PG_REQUIRE(handle); @@ -368,7 +375,7 @@ filesystem_metadata_tests(void) make_path(path, "name1.txt"); fd = open(path, O_CREAT | O_EXCL | O_RDWR | PG_BINARY, 0777); PG_EXPECT_SYS(fd >= 0, "touch name1.txt"); - PG_EXPECT_SYS(close(fd) == 0); + PG_REQUIRE_SYS(fd < 0 || close(fd) == 0); make_path(path2, "name2.txt"); PG_EXPECT_SYS(rename(path, path2) == 0, "rename name1.txt -> name2.txt"); @@ -378,14 +385,14 @@ filesystem_metadata_tests(void) fd = open(path, O_CREAT | O_EXCL | O_RDWR | PG_BINARY, 0777); PG_EXPECT_SYS(fd >= 0, "touch name1.txt"); - PG_EXPECT_SYS(close(fd) == 0); + PG_REQUIRE_SYS(fd < 0 || close(fd) == 0); make_path(path2, "name2.txt"); PG_EXPECT_SYS(rename(path, path2) == 0, "rename name1.txt -> name2.txt, replacing it"); fd = open(path, O_CREAT | O_EXCL | O_RDWR | PG_BINARY, 0777); PG_EXPECT_SYS(fd >= 0, "touch name1.txt"); - PG_EXPECT_SYS(close(fd) == 0); + PG_REQUIRE_SYS(fd < 0 || close(fd) == 0); fd = open(path2, O_RDWR | PG_BINARY, 0777); PG_EXPECT_SYS(fd >= 0, "open name2.txt"); @@ -414,12 +421,12 @@ filesystem_metadata_tests(void) make_path(path2, "name2.txt"); PG_EXPECT_SYS(rename(path, path2) == 0, "can rename name1.txt -> name2.txt while name1.txt is open"); - PG_EXPECT_SYS(close(fd) == 0); + PG_REQUIRE_SYS(fd < 0 || close(fd) == 0); make_path(path, "name1.txt"); fd = open(path, O_CREAT | O_EXCL | O_RDWR | PG_BINARY, 0777); PG_EXPECT_SYS(fd >= 0, "touch name1.txt"); - PG_EXPECT_SYS(close(fd) == 0); + PG_REQUIRE_SYS(fd < 0 || close(fd) == 0); fd = open(path2, O_RDWR | PG_BINARY, 0777); PG_EXPECT_SYS(fd >= 0, "open name2.txt"); @@ -435,9 +442,9 @@ filesystem_metadata_tests(void) PG_EXPECT_SYS(rename(path, path2) == -1, "Windows non-POSIX: cannot rename name1.txt -> name2.txt while unlinked file is still open"); PG_EXPECT_EQ(errno, EACCES); - PG_REQUIRE_SYS(unlink(path) == 0); + PG_EXPECT_SYS(unlink(path) == 0); } - PG_REQUIRE_SYS(close(fd) == 0); + PG_REQUIRE_SYS(fd < 0 || close(fd) == 0); #ifdef WIN32 diff --git a/src/port/win32error.c b/src/port/win32error.c index a78d323827..67ce805d77 100644 --- a/src/port/win32error.c +++ b/src/port/win32error.c @@ -167,6 +167,9 @@ static const struct }, { ERROR_INVALID_NAME, ENOENT + }, + { + ERROR_CANT_RESOLVE_FILENAME, ENOENT } }; diff --git a/src/port/win32stat.c b/src/port/win32stat.c index 5f3d0d22ff..ce8d87093d 100644 --- a/src/port/win32stat.c +++ b/src/port/win32stat.c @@ -125,15 +125,30 @@ _pglstat64(const char *name, struct stat *buf) hFile = pgwin32_open_handle(name, O_RDONLY, true); if (hFile == INVALID_HANDLE_VALUE) - return -1; - - ret = fileinfo_to_stat(hFile, buf); + { + if (errno == ENOENT) + { + /* + * If it's a junction point pointing to a non-existent path, we'll + * have ENOENT here (because pgwin32_open_handle does not use + * FILE_FLAG_OPEN_REPARSE_POINT). In that case, we'll try again + * with readlink() below, which will distinguish true ENOENT from + * pseudo-symlink. + */ + memset(buf, 0, sizeof(*buf)); + ret = 0; + } + else + return -1; + } + else + ret = fileinfo_to_stat(hFile, buf); /* * Junction points appear as directories to fileinfo_to_stat(), so we'll * need to do a bit more work to distinguish them. */ - if (ret == 0 && S_ISDIR(buf->st_mode)) + if ((ret == 0 && S_ISDIR(buf->st_mode)) || hFile == INVALID_HANDLE_VALUE) { char next[MAXPGPATH]; ssize_t size; @@ -169,10 +184,12 @@ _pglstat64(const char *name, struct stat *buf) buf->st_mode &= ~S_IFDIR; buf->st_mode |= S_IFLNK; buf->st_size = size; + ret = 0; } } - CloseHandle(hFile); + if (hFile != INVALID_HANDLE_VALUE) + CloseHandle(hFile); return ret; } -- 2.35.1