aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorEike Ziller <[email protected]>2025-08-27 15:28:44 +0200
committerEike Ziller <[email protected]>2025-09-17 09:04:26 +0000
commit968135b74b660ac5849329c68276ded691c2f501 (patch)
treee7e793dde4eb10193be8683679b55609f9df0513
parent38f83bad7e718a65d520cef9462d149d73634ad7 (diff)
FilePath: Fix resolution of symlinks with directory links
In the situation /a/file3.txt /x/linktest1.txt -> ../a/file3.txt /a/dirlink -> ../x unfortunately symLinkTarget("/a/dirlink/linktest1.txt") returns "/a/a/file3.txt", because readlink of that file returns "../a/file3.txt" and that relative path is then resolved via the original path, "/a/dirlink/../a/file3.txt". It does not recognize that a directory link is involved that changes the depth of the hierarchy. That also breaks FilePath::resolveSymLinks that relies on symLinkTarget. Fix it by checking the whole parent hierarchy of the path for symlinks instead of just the file itself. If a link is found, that counts towards the max 16 jumps that it does. Adds some tests for resolveSymLinks. Fixes: QTCREATORBUG-33335 Change-Id: Id55ae44cc4e8fabaf6ae37e87b0bc7d07e032d21 Reviewed-by: Marcus Tillmanns <[email protected]>
-rw-r--r--src/libs/utils/filepath.cpp18
-rw-r--r--tests/auto/utils/filepath/tst_filepath.cpp52
2 files changed, 67 insertions, 3 deletions
diff --git a/src/libs/utils/filepath.cpp b/src/libs/utils/filepath.cpp
index 37e36030253..1df6878e0b6 100644
--- a/src/libs/utils/filepath.cpp
+++ b/src/libs/utils/filepath.cpp
@@ -2493,10 +2493,22 @@ FilePath FilePath::resolveSymlinks() const
FilePath current = *this;
int links = 16;
while (links--) {
- const FilePath target = current.symLinkTarget();
- if (target.isEmpty())
+ const QList<QStringView> components = current.pathComponents();
+ FilePath pathToTest = current.withNewPath("");
+ bool resolved = false;
+ for (const QStringView &path : components) {
+ pathToTest = pathToTest / QString(path);
+ if (!resolved) {
+ const FilePath target = pathToTest.symLinkTarget();
+ if (!target.isEmpty()) {
+ resolved = true;
+ pathToTest = target;
+ }
+ }
+ }
+ if (!resolved)
return current;
- current = target;
+ current = pathToTest;
}
return current;
}
diff --git a/tests/auto/utils/filepath/tst_filepath.cpp b/tests/auto/utils/filepath/tst_filepath.cpp
index 9f64517e1d5..1770c219ed2 100644
--- a/tests/auto/utils/filepath/tst_filepath.cpp
+++ b/tests/auto/utils/filepath/tst_filepath.cpp
@@ -140,6 +140,7 @@ private slots:
void pathComponents_data();
void symLinks();
+ void resolveSymLinks();
void ensureWritableDirectory();
void ensureWritableDirectoryPermissions();
@@ -2017,6 +2018,57 @@ void tst_filepath::symLinks()
QCOMPARE(link.symLinkTarget(), orig);
}
+void tst_filepath::resolveSymLinks()
+{
+ if (HostOsInfo::isWindowsHost())
+ QSKIP("Creating symbolic links requires special privileges on Windows");
+
+ // relative link chain to existing file
+ const FilePath orig1 = FilePath::fromString(rootPath).pathAppended("a/file3.txt");
+ QVERIFY(orig1.exists());
+ const FilePath link1 = FilePath::fromString(rootPath).pathAppended("x/linktest1.txt");
+ const Result<> res1 = FilePath::fromString("../a/file3.txt").createSymLink(link1);
+ QVERIFY_RESULT(res1);
+ QVERIFY(link1.isSymLink());
+ QCOMPARE(link1.symLinkTarget(), orig1);
+ const FilePath link2 = FilePath::fromString(rootPath).pathAppended("x/linktest2.txt");
+ const Result<> res2 = FilePath::fromString("linktest1.txt").createSymLink(link2);
+ QVERIFY_RESULT(res2);
+ QVERIFY(link2.isSymLink());
+ QCOMPARE(link2.symLinkTarget(), link1);
+ QCOMPARE(link2.resolveSymlinks(), orig1);
+
+ // relative link chain to non-existing file
+ const FilePath orig2 = FilePath::fromString(rootPath).pathAppended("a/broken.txt");
+ QVERIFY(!orig2.exists());
+ const FilePath link3 = FilePath::fromString(rootPath).pathAppended("x/linktest3.txt");
+ const Result<> res3 = FilePath::fromString("../a/broken.txt").createSymLink(link3);
+ QVERIFY_RESULT(res3);
+ QVERIFY(link3.isSymLink());
+ QCOMPARE(link3.symLinkTarget(), orig2);
+ const FilePath link4 = FilePath::fromString(rootPath).pathAppended("x/linktest4.txt");
+ const Result<> res4 = FilePath::fromString("linktest3.txt").createSymLink(link4);
+ QVERIFY_RESULT(res4);
+ QVERIFY(link4.isSymLink());
+ QCOMPARE(link4.symLinkTarget(), link3);
+ QCOMPARE(link4.resolveSymlinks(), orig2);
+
+ // relative links in directory links
+ // currently:
+ // <tmp>/a/file3.txt
+ // <tmp>/x/linktest1.txt -> ../a/file3.txt
+ // if now:
+ // <tmp>/a/dirlink -> ../x
+ // then resolution should happen like this:
+ // <tmp>/a/dirlink/linktest1.txt -> <tmp>/a/file3.txt
+ const FilePath dirlink = FilePath::fromString(rootPath).pathAppended("a/dirlink");
+ const Result<> res5 = FilePath::fromString("../x").createSymLink(dirlink);
+ QVERIFY_RESULT(res5);
+ QVERIFY(dirlink.isSymLink());
+ QCOMPARE(dirlink.symLinkTarget(), FilePath::fromString(rootPath).pathAppended("x"));
+ QCOMPARE(dirlink.pathAppended("linktest1.txt").resolveSymlinks(), orig1);
+}
+
void tst_filepath::ensureWritableDirectory()
{
const FilePath dir = FilePath::fromString(rootPath).pathAppended("x/y/writableDir");