gulrak / filesystem

An implementation of C++17 std::filesystem for C++11 /C++14/C++17/C++20 on Windows, macOS, Linux and FreeBSD.
MIT License
1.34k stars 173 forks source link

Cannot remove read permissions on Windows 10 #99

Closed Talkless closed 3 years ago

Talkless commented 3 years ago

I use QFile::setPermissions() in a unit test where I want to imitate handling of "permission denied" error for some file operations, but that works only on unix-like systems...

Instead of dealing with WinAPI directly, I though I would try using this neat header-only lib.

But it seems the change I am getting at best is changing 666 to 444 (seen using ghc::filesystem::status()), either with:

fs::permissions(path, fs::perms::none, fs::perm_options::replace)

or with:

fs::permissions(path, fs::perms::all, fs::perm_options::remove);

I understand that it "most certainly not lead to the expected behavior", but it seems it tries to change something, maybe I am doing something wrong?

Same with 1.4.0 (from Conan) and 1.5.0. Building with Visual Studio 2017 in x86 mode.

Talkless commented 3 years ago

Oh, VS 2017 do support <filesystem>, and in that case it changes 777 to 555 (still allows reading).

I guess I just won't be able to achieve that in posix-first std::filesystem?

gulrak commented 3 years ago

Indeed, neither my implementation nor those from MSVC are able to do that with the fs::permissions API, the simple chmod type of POSIX permissions is not very expressive and there is no simple Read-Flag in Windows that can be removed, just a FILE_ATTRIBUTE_READONLY. (See also the Answer of one of the developers behind the MS std::filesystem implementation here.)

Talkless commented 3 years ago

I implemented my helper function to set DENY_ACCESS for EVERYONE using SetEntriesInAclA by some MSDN example like this:

void TestBackup::removePermissionsOrFatal(const QString &path)
{
#ifdef Q_OS_WINDOWS

    auto nativePath{QDir::toNativeSeparators(path).toStdString()};

    struct ScopedPACL {

        ~ScopedPACL() {
          if (pointer)
              LocalFree(static_cast<HLOCAL>(pointer));
        };

        PACL pointer{nullptr};
    };

    struct ScopedSecurityDescriptor {

        ~ScopedSecurityDescriptor() {
          if (pointer)
              LocalFree(static_cast<HLOCAL>(pointer));
        };

        PSECURITY_DESCRIPTOR pointer{nullptr};
    };

    DWORD dwRes{};
    ScopedPACL oldPacl;
    ScopedSecurityDescriptor sd;

    dwRes = GetNamedSecurityInfoA(nativePath.c_str(),
                                  SE_FILE_OBJECT,
                                  DACL_SECURITY_INFORMATION,
                                  nullptr,
                                  nullptr,
                                  &oldPacl.pointer,
                                  nullptr,
                                  &sd.pointer);

    if (dwRes != ERROR_SUCCESS) {
        qFatal( "GetNamedSecurityInfo Error %u\n", dwRes);
    }

    std::string trusteeName{"EVERYONE"}; // we need non-const char*, cannot use string literal...
    EXPLICIT_ACCESS ea{};
    ea.grfAccessPermissions = GENERIC_ALL;
    ea.grfAccessMode = DENY_ACCESS;
    ea.grfInheritance = 0;
    ea.Trustee.TrusteeForm = TRUSTEE_IS_NAME;
    ea.Trustee.ptstrName = trusteeName.data();

    ScopedPACL newDACL;
    dwRes = SetEntriesInAclA(1, &ea, oldPacl.pointer, &newDACL.pointer);

    if (dwRes != ERROR_SUCCESS) {
        qFatal("SetEntriesInAcl Error %u", dwRes);
    }

    dwRes = SetNamedSecurityInfoA(nativePath.data(),
                                  SE_FILE_OBJECT,
                                  DACL_SECURITY_INFORMATION,
                                  nullptr,
                                  nullptr,
                                  newDACL.pointer,
                                  nullptr);

    if (dwRes != ERROR_SUCCESS) {
        qFatal("SetNamedSecurityInfo Error %u\n", dwRes);
    }

#else

    if (!QFile::setPermissions(path, {})) {
        qFatal("Failed to change permissions for file '%s'", qPrintable(path));
    }

#endif
}
gulrak commented 3 years ago

Nice that you found a solution and thank you for sharing it! I currently have no plan to implement ACL based permission handling as the MSVC version does not, but I might decide to change that and it sure will be helpful.