From 9366413d3ae3cc5abc9fe7546549ca08d6add7ee Mon Sep 17 00:00:00 2001 From: Thomas Munro Date: Thu, 11 Aug 2022 12:30:48 +1200 Subject: [PATCH v2 08/10] Fix stat() for recursive junction points on Windows. Commit c5cb8f3b supposed that we'd only ever have to follow one junction point in stat(), because we don't construct longer chains of them ourselves. When examining a parent directory supplied by the user, we should really be able to cope with longer chains, just in case someone has their system set up that way. Choose an arbitrary cap of 8, to match the minimum acceptable value of SYMLOOP_MAX in POSIX. Previously I'd avoided reporting ELOOP thinking Windows didn't have it, but it turns out that it does. Reviewed-by: Roman Zharkov Discussion: https://postgr.es/m/CA%2BhUKGJ7JDGWYFt9%3D-TyJiRRy5q9TtPfqeKkneWDr1XPU1%2Biqw%40mail.gmail.com --- src/port/t/001_filesystem.c | 55 +++++++++++++++++++++++++++++++++++++ src/port/win32stat.c | 26 +++++++++--------- 2 files changed, 68 insertions(+), 13 deletions(-) diff --git a/src/port/t/001_filesystem.c b/src/port/t/001_filesystem.c index de038a86f6..35abeb772b 100644 --- a/src/port/t/001_filesystem.c +++ b/src/port/t/001_filesystem.c @@ -305,6 +305,61 @@ filesystem_metadata_tests(void) PG_EXPECT(stat(path, &statbuf) == 0, "stat symlink"); PG_EXPECT(S_ISDIR(statbuf.st_mode)); + /* Recursive symlinks. */ + make_path(path, "dir1"); + make_path(path2, "sym001"); + PG_EXPECT_SYS(symlink(path, path2) == 0, "sym001 -> dir1"); + make_path(path, "sym001"); + make_path(path2, "sym002"); + PG_EXPECT_SYS(symlink(path, path2) == 0, "sym002 -> sym001"); + make_path(path, "sym002"); + make_path(path2, "sym003"); + PG_EXPECT_SYS(symlink(path, path2) == 0, "sym003 -> sym002"); + make_path(path, "sym003"); + make_path(path2, "sym004"); + PG_EXPECT_SYS(symlink(path, path2) == 0, "sym004 -> sym003"); + make_path(path, "sym004"); + make_path(path2, "sym005"); + PG_EXPECT_SYS(symlink(path, path2) == 0, "sym005 -> sym004"); + make_path(path, "sym005"); + make_path(path2, "sym006"); + PG_EXPECT_SYS(symlink(path, path2) == 0, "sym006 -> sym005"); + make_path(path, "sym006"); + make_path(path2, "sym007"); + PG_EXPECT_SYS(symlink(path, path2) == 0, "sym007 -> sym006"); + make_path(path, "sym007"); + make_path(path2, "sym008"); + PG_EXPECT_SYS(symlink(path, path2) == 0, "sym008 -> sym007"); + make_path(path, "sym008"); + make_path(path2, "sym009"); + PG_EXPECT_SYS(symlink(path, path2) == 0, "sym009 -> sym008"); + + /* POSIX says SYMLOOP_MAX should be at least 8. */ + make_path(path, "sym008"); + memset(&statbuf, 0, sizeof(statbuf)); + PG_EXPECT_SYS(stat(path, &statbuf) == 0, "stat sym008"); + PG_EXPECT(S_ISDIR(statbuf.st_mode)); + +#ifdef WIN32 + + /* + * Test ELOOP failure in our Windows implementation of stat(), because we + * know it gives up after 8. + */ + make_path(path, "sym009"); + memset(&statbuf, 0, sizeof(statbuf)); + PG_EXPECT(stat(path, &statbuf) == -1, "Windows: stat sym009 fails"); + PG_EXPECT_EQ(errno, ELOOP); +#endif + + /* If we break the chain we get ENOENT. */ + make_path(path, "sym003"); + PG_EXPECT_SYS(unlink(path) == 0); + make_path(path, "sym008"); + memset(&statbuf, 0, sizeof(statbuf)); + PG_EXPECT(stat(path, &statbuf) == -1, "stat broken symlink chain fails"); + PG_EXPECT_EQ(errno, ENOENT); + /* Tests for lstat(). */ PG_EXPECT(stat("does-not-exist.txt", &statbuf) == -1, "lstat missing file fails"); diff --git a/src/port/win32stat.c b/src/port/win32stat.c index ce8d87093d..e6553e4030 100644 --- a/src/port/win32stat.c +++ b/src/port/win32stat.c @@ -199,23 +199,33 @@ _pglstat64(const char *name, struct stat *buf) int _pgstat64(const char *name, struct stat *buf) { + int loops = 0; int ret; + char curr[MAXPGPATH]; ret = _pglstat64(name, buf); + strlcpy(curr, name, MAXPGPATH); + /* Do we need to follow a symlink (junction point)? */ - if (ret == 0 && S_ISLNK(buf->st_mode)) + while (ret == 0 && S_ISLNK(buf->st_mode)) { char next[MAXPGPATH]; ssize_t size; + if (++loops > 8) + { + errno = ELOOP; + return -1; + } + /* * _pglstat64() already called readlink() once to be able to fill in * st_size, and now we need to do it again to get the path to follow. * That could be optimized, but stat() on symlinks is probably rare * and this way is simple. */ - size = readlink(name, next, sizeof(next)); + size = readlink(curr, next, sizeof(next)); if (size < 0) { if (errno == EACCES && @@ -234,17 +244,7 @@ _pgstat64(const char *name, struct stat *buf) next[size] = 0; ret = _pglstat64(next, buf); - if (ret == 0 && S_ISLNK(buf->st_mode)) - { - /* - * We're only prepared to go one hop, because we only expect to - * deal with the simple cases that we create. The error for too - * many symlinks is supposed to be ELOOP, but Windows hasn't got - * it. - */ - errno = EIO; - return -1; - } + strcpy(curr, next); } return ret; -- 2.35.1