diff --git a/api/logic/mojang/PackageManifest.cpp b/api/logic/mojang/PackageManifest.cpp index 0b420568..b3dfd7fc 100644 --- a/api/logic/mojang/PackageManifest.cpp +++ b/api/logic/mojang/PackageManifest.cpp @@ -5,6 +5,12 @@ #include #include +#ifndef Q_OS_WIN32 +#include +#include +#include +#endif + namespace mojang_files { const Hash hash_of_empty_string = "da39a3ee5e6b4b0d3255bfef95601890afd80709"; @@ -177,6 +183,49 @@ Package Package::fromManifestFile(const QString & filename) { } } +#ifndef Q_OS_WIN32 + +#include +#include +#include + +namespace { +// FIXME: Qt obscures symlink targets by making them absolute. that is useless. this is the workaround - we do it ourselves +bool actually_read_symlink_target(const QString & filepath, Path & out) +{ + struct ::stat st; + // FIXME: here, we assume the native filesystem encoding. May the Gods have mercy upon our Souls. + QByteArray nativePath = filepath.toUtf8(); + const char * filepath_cstr = nativePath.data(); + + if (lstat(filepath_cstr, &st) != 0) + { + return false; + } + + auto size = st.st_size ? st.st_size + 1 : PATH_MAX; + std::string temp(size, '\0'); + // because we don't realiably know how long the damn thing actually is, we loop and expand. POSIX is naff + do + { + auto link_length = ::readlink(filepath_cstr, &temp[0], temp.size()); + if(link_length == -1) + { + return false; + } + if(std::string::size_type(link_length) < temp.size()) + { + // buffer was long enough and we managed to read the link target. RETURN here. + temp.resize(link_length); + out = Path(QString::fromUtf8(temp.c_str())); + return true; + } + temp.resize(temp.size() * 2); + } while (true); +} +} +#endif + // FIXME: Qt filesystem abstraction is bad, but ... let's hope it doesn't break too much? // FIXME: The error handling is just DEFICIENT Package Package::fromInspectedFolder(const QString& folderPath) @@ -190,10 +239,22 @@ Package Package::fromInspectedFolder(const QString& folderPath) auto fileInfo = iterator.fileInfo(); auto relPath = root.relativeFilePath(fileInfo.filePath()); + // FIXME: this is probably completely busted on Windows anyway, so just disable it. + // Qt makes shit up and doesn't understand the platform details + // TODO: Actually use a filesystem library that isn't terrible and has decen license. + // I only know one, and I wrote it. Sadly, currently proprietary. PAIN. +#ifndef Q_OS_WIN32 if(fileInfo.isSymLink()) { - out.addLink(relPath, fileInfo.symLinkTarget()); + Path targetPath; + if(!actually_read_symlink_target(fileInfo.filePath(), targetPath)) { + qCritical() << "Folder inspection: Unknown filesystem object:" << fileInfo.absoluteFilePath(); + out.valid = false; + } + out.addLink(relPath, targetPath); } - else if(fileInfo.isDir()) { + else +#endif + if(fileInfo.isDir()) { out.addFolder(relPath); } else if(fileInfo.isFile()) { diff --git a/api/logic/mojang/PackageManifest_test.cpp b/api/logic/mojang/PackageManifest_test.cpp index 178de5dc..d4c55c5a 100644 --- a/api/logic/mojang/PackageManifest_test.cpp +++ b/api/logic/mojang/PackageManifest_test.cpp @@ -20,8 +20,9 @@ private slots: void test_parse(); void test_parse_file(); void test_inspect(); - void test_diff(); - +#ifndef Q_OS_WIN32 + void test_inspect_symlinks(); +#endif void mkdir_deep(); void rmdir_deep(); @@ -87,7 +88,31 @@ void PackageManifestTest::test_parse_file() { QVERIFY(manifest.valid == true); } + void PackageManifestTest::test_inspect() { + auto path = QFINDTESTDATA("testdata/inspect_win/"); + auto manifest = Package::fromInspectedFolder(path); + QVERIFY(manifest.valid == true); + QVERIFY(manifest.files.size() == 2); + QVERIFY(manifest.files.count(Path("a/b.txt"))); + auto &file1 = manifest.files[Path("a/b.txt")]; + QVERIFY(file1.executable == false); + QVERIFY(file1.hash == "da39a3ee5e6b4b0d3255bfef95601890afd80709"); + QVERIFY(file1.size == 0); + QVERIFY(manifest.files.count(Path("a/b/b.txt"))); + auto &file2 = manifest.files[Path("a/b/b.txt")]; + QVERIFY(file2.executable == false); + QVERIFY(file2.hash == "da39a3ee5e6b4b0d3255bfef95601890afd80709"); + QVERIFY(file2.size == 0); + QVERIFY(manifest.folders.size() == 3); + QVERIFY(manifest.folders.count(Path("."))); + QVERIFY(manifest.folders.count(Path("a"))); + QVERIFY(manifest.folders.count(Path("a/b"))); + QVERIFY(manifest.symlinks.size() == 0); +} + +#ifndef Q_OS_WIN32 +void PackageManifestTest::test_inspect_symlinks() { auto path = QFINDTESTDATA("testdata/inspect/"); auto manifest = Package::fromInspectedFolder(path); QVERIFY(manifest.valid == true); @@ -102,25 +127,11 @@ void PackageManifestTest::test_inspect() { QVERIFY(manifest.folders.count(Path("a"))); QVERIFY(manifest.folders.count(Path("a/b"))); QVERIFY(manifest.symlinks.size() == 1); + QVERIFY(manifest.symlinks.count(Path("a/b/b.txt"))); + qDebug() << manifest.symlinks[Path("a/b/b.txt")]; + QVERIFY(manifest.symlinks[Path("a/b/b.txt")] == Path("../b.txt")); } - -void PackageManifestTest::test_diff() { - auto path = QFINDTESTDATA("testdata/inspect/"); - auto from = Package::fromInspectedFolder(path); - auto to = Package::fromManifestContents(basic_manifest); - auto operations = UpdateOperations::resolve(from, to); - QVERIFY(operations.valid == true); - QVERIFY(operations.mkdirs.size() == 1); - QVERIFY(operations.mkdirs[0] == Path("a/b/c")); - - QVERIFY(operations.rmdirs.size() == 0); - QVERIFY(operations.deletes.size() == 1); - QVERIFY(operations.deletes[0] == Path("a/b/b.txt")); - QVERIFY(operations.downloads.size() == 0); - QVERIFY(operations.mklinks.size() == 1); - QVERIFY(operations.mklinks.count(Path("a/b/c.txt"))); - QVERIFY(operations.mklinks[Path("a/b/c.txt")] == Path("../b.txt")); -} +#endif void PackageManifestTest::mkdir_deep() { diff --git a/api/logic/mojang/testdata/inspect_win/a/b.txt b/api/logic/mojang/testdata/inspect_win/a/b.txt new file mode 100644 index 00000000..e69de29b diff --git a/api/logic/mojang/testdata/inspect_win/a/b/b.txt b/api/logic/mojang/testdata/inspect_win/a/b/b.txt new file mode 100644 index 00000000..e69de29b