diff --git a/src/App/Document.cpp b/src/App/Document.cpp index 41af73e1a5..aa46a3939c 100644 --- a/src/App/Document.cpp +++ b/src/App/Document.cpp @@ -1792,6 +1792,12 @@ bool Document::saveToFile(const char* filename) const // realpath is canonical filename i.e. without symlink std::string nativePath = canonical_path(filename); + // check if file is writeable, then block the save if it is not. + Base::FileInfo originalFileInfo(nativePath); + if (originalFileInfo.exists() && !originalFileInfo.isWritable()) { + throw Base::FileException("Unable to save document because file is marked as read-only or write permission is not available.", originalFileInfo); + } + // make a tmp. file where to save the project data first and then rename to // the actual file name. This may be useful if overwriting an existing file // fails so that the data of the work up to now isn't lost. diff --git a/src/Base/FileInfo.cpp b/src/Base/FileInfo.cpp index 1258d5bdf8..05983c8956 100644 --- a/src/Base/FileInfo.cpp +++ b/src/Base/FileInfo.cpp @@ -336,6 +336,44 @@ bool FileInfo::isWritable() const if (fs::is_directory(path)) { return directoryIsWritable(path); } +#ifdef FC_OS_WIN32 + // convert filename from UTF-8 to windows WSTRING + std::wstring fileNameWstring = toStdWString(); + // requires import of + DWORD attributes = GetFileAttributes(fileNameWstring.c_str()); + if (attributes == INVALID_FILE_ATTRIBUTES) { + // Log the error? + std::clog << "GetFileAttributes failed for file: " << FileName << '\n'; + // usually indicates some kind of network file issue, so the file is probably not writable + return false; + } + if ((attributes & FILE_ATTRIBUTE_READONLY) != 0) { + return false; + } + // TEST if file is truly writable, because windows ACL does not map well to POSIX perms, + // and there are other potential blockers (app or shared file locks, etc) + HANDLE hFile = CreateFileW( + fileNameWstring.c_str(), + GENERIC_WRITE, + 0, // ----> no sharing: fail if anyone else has it open + nullptr, + OPEN_EXISTING, + FILE_ATTRIBUTE_NORMAL, + nullptr + ); + + if (hFile == INVALID_HANDLE_VALUE) { + DWORD err = GetLastError(); + if (err == ERROR_SHARING_VIOLATION || err == ERROR_LOCK_VIOLATION) { + return false; + } + return false; + } + if (!CloseHandle(hFile)) { + std::clog << "CloseHandle failed for file: " << FileName + << " while checking for write access." << '\n'; + } +#endif fs::file_status stat = fs::status(path); fs::perms perms = stat.permissions(); return (perms & fs::perms::owner_write) == fs::perms::owner_write;