Reworked the rename_file() function on Windows to work reliably and
atomically. The code was taken from the llvm project, it is complex and hopefully it covers all the Windows file system quirks. Vojtech has highest hopes, that this will fix the various PrusaSlicer.ini file corruptions. Enabled the locales switching and error handling on Linux as well, where now the missing locales are reported and running the locale-gen tool is recommended.
This commit is contained in:
parent
a83da0f72c
commit
fd3fe75d1c
6 changed files with 248 additions and 62 deletions
|
@ -66,7 +66,12 @@ int CLI::run(int argc, char **argv)
|
|||
boost::nowide::nowide_filesystem();
|
||||
} catch (const std::runtime_error& ex) {
|
||||
std::string caption = std::string(SLIC3R_APP_NAME) + " Error";
|
||||
std::string text = std::string("An error occured while setting up locale.\n") + SLIC3R_APP_NAME + " will now terminate.\n\n" + ex.what();
|
||||
std::string text = std::string("An error occured while setting up locale.\n") + (
|
||||
#if !defined(_WIN32) && !defined(__APPLE__)
|
||||
// likely some linux system
|
||||
"You may need to reconfigure the missing locales, likely by running the \"locale-gen\"" and \"dpkg-reconfigure locales\" commands.\n"
|
||||
#endif
|
||||
SLIC3R_APP_NAME " will now terminate.\n\n") + ex.what();
|
||||
#ifdef SLIC3R_GUI
|
||||
if (m_actions.empty())
|
||||
MessageBoxA(NULL, text.c_str(), caption.c_str(), MB_OK | MB_ICONERROR);
|
||||
|
@ -426,7 +431,7 @@ int CLI::run(int argc, char **argv)
|
|||
outfile_final = sla_print.print_statistics().finalize_output_path(outfile);
|
||||
sla_print.export_raster(outfile_final);
|
||||
}
|
||||
if (outfile != outfile_final && Slic3r::rename_file(outfile, outfile_final) != 0) {
|
||||
if (outfile != outfile_final && Slic3r::rename_file(outfile, outfile_final)) {
|
||||
boost::nowide::cerr << "Renaming file " << outfile << " to " << outfile_final << " failed" << std::endl;
|
||||
return 1;
|
||||
}
|
||||
|
|
|
@ -607,7 +607,7 @@ void GCode::do_export(Print *print, const char *path, GCodePreviewData *preview_
|
|||
m_analyzer.reset();
|
||||
}
|
||||
|
||||
if (rename_file(path_tmp, path) != 0)
|
||||
if (rename_file(path_tmp, path))
|
||||
throw std::runtime_error(
|
||||
std::string("Failed to rename the output G-code file from ") + path_tmp + " to " + path + '\n' +
|
||||
"Is " + path_tmp + " locked?" + '\n');
|
||||
|
|
|
@ -390,7 +390,7 @@ namespace Slic3r {
|
|||
fclose(out);
|
||||
in.close();
|
||||
|
||||
if (rename_file(path_tmp, filename) != 0)
|
||||
if (rename_file(path_tmp, filename))
|
||||
throw std::runtime_error(std::string("Failed to rename the output G-code file from ") + path_tmp + " to " + filename + '\n' +
|
||||
"Is " + path_tmp + " locked?" + '\n');
|
||||
|
||||
|
|
|
@ -61,7 +61,7 @@ extern std::string normalize_utf8_nfc(const char *src);
|
|||
// Safely rename a file even if the target exists.
|
||||
// On Windows, the file explorer (or anti-virus or whatever else) often locks the file
|
||||
// for a short while, so the file may not be movable. Retry while we see recoverable errors.
|
||||
extern int rename_file(const std::string &from, const std::string &to);
|
||||
extern std::error_code rename_file(const std::string &from, const std::string &to);
|
||||
|
||||
// Copy a file, adjust the access attributes, so that the target is writable.
|
||||
extern int copy_file(const std::string &from, const std::string &to);
|
||||
|
|
|
@ -173,67 +173,247 @@ const std::string& data_dir()
|
|||
return g_data_dir;
|
||||
}
|
||||
|
||||
#ifdef _WIN32
|
||||
// The following helpers are borrowed from the LLVM project https://github.com/llvm
|
||||
namespace WindowsSupport
|
||||
{
|
||||
template <typename HandleTraits>
|
||||
class ScopedHandle {
|
||||
typedef typename HandleTraits::handle_type handle_type;
|
||||
handle_type Handle;
|
||||
ScopedHandle(const ScopedHandle &other) = delete;
|
||||
void operator=(const ScopedHandle &other) = delete;
|
||||
public:
|
||||
ScopedHandle() : Handle(HandleTraits::GetInvalid()) {}
|
||||
explicit ScopedHandle(handle_type h) : Handle(h) {}
|
||||
~ScopedHandle() { if (HandleTraits::IsValid(Handle)) HandleTraits::Close(Handle); }
|
||||
handle_type take() {
|
||||
handle_type t = Handle;
|
||||
Handle = HandleTraits::GetInvalid();
|
||||
return t;
|
||||
}
|
||||
ScopedHandle &operator=(handle_type h) {
|
||||
if (HandleTraits::IsValid(Handle))
|
||||
HandleTraits::Close(Handle);
|
||||
Handle = h;
|
||||
return *this;
|
||||
}
|
||||
// True if Handle is valid.
|
||||
explicit operator bool() const { return HandleTraits::IsValid(Handle) ? true : false; }
|
||||
operator handle_type() const { return Handle; }
|
||||
};
|
||||
|
||||
struct CommonHandleTraits {
|
||||
typedef HANDLE handle_type;
|
||||
static handle_type GetInvalid() { return INVALID_HANDLE_VALUE; }
|
||||
static void Close(handle_type h) { ::CloseHandle(h); }
|
||||
static bool IsValid(handle_type h) { return h != GetInvalid(); }
|
||||
};
|
||||
|
||||
typedef ScopedHandle<CommonHandleTraits> ScopedFileHandle;
|
||||
|
||||
std::error_code map_windows_error(unsigned windows_error_code)
|
||||
{
|
||||
#define MAP_ERR_TO_COND(x, y) case x: return std::make_error_code(std::errc::y)
|
||||
switch (windows_error_code) {
|
||||
MAP_ERR_TO_COND(ERROR_ACCESS_DENIED, permission_denied);
|
||||
MAP_ERR_TO_COND(ERROR_ALREADY_EXISTS, file_exists);
|
||||
MAP_ERR_TO_COND(ERROR_BAD_UNIT, no_such_device);
|
||||
MAP_ERR_TO_COND(ERROR_BUFFER_OVERFLOW, filename_too_long);
|
||||
MAP_ERR_TO_COND(ERROR_BUSY, device_or_resource_busy);
|
||||
MAP_ERR_TO_COND(ERROR_BUSY_DRIVE, device_or_resource_busy);
|
||||
MAP_ERR_TO_COND(ERROR_CANNOT_MAKE, permission_denied);
|
||||
MAP_ERR_TO_COND(ERROR_CANTOPEN, io_error);
|
||||
MAP_ERR_TO_COND(ERROR_CANTREAD, io_error);
|
||||
MAP_ERR_TO_COND(ERROR_CANTWRITE, io_error);
|
||||
MAP_ERR_TO_COND(ERROR_CURRENT_DIRECTORY, permission_denied);
|
||||
MAP_ERR_TO_COND(ERROR_DEV_NOT_EXIST, no_such_device);
|
||||
MAP_ERR_TO_COND(ERROR_DEVICE_IN_USE, device_or_resource_busy);
|
||||
MAP_ERR_TO_COND(ERROR_DIR_NOT_EMPTY, directory_not_empty);
|
||||
MAP_ERR_TO_COND(ERROR_DIRECTORY, invalid_argument);
|
||||
MAP_ERR_TO_COND(ERROR_DISK_FULL, no_space_on_device);
|
||||
MAP_ERR_TO_COND(ERROR_FILE_EXISTS, file_exists);
|
||||
MAP_ERR_TO_COND(ERROR_FILE_NOT_FOUND, no_such_file_or_directory);
|
||||
MAP_ERR_TO_COND(ERROR_HANDLE_DISK_FULL, no_space_on_device);
|
||||
MAP_ERR_TO_COND(ERROR_INVALID_ACCESS, permission_denied);
|
||||
MAP_ERR_TO_COND(ERROR_INVALID_DRIVE, no_such_device);
|
||||
MAP_ERR_TO_COND(ERROR_INVALID_FUNCTION, function_not_supported);
|
||||
MAP_ERR_TO_COND(ERROR_INVALID_HANDLE, invalid_argument);
|
||||
MAP_ERR_TO_COND(ERROR_INVALID_NAME, invalid_argument);
|
||||
MAP_ERR_TO_COND(ERROR_LOCK_VIOLATION, no_lock_available);
|
||||
MAP_ERR_TO_COND(ERROR_LOCKED, no_lock_available);
|
||||
MAP_ERR_TO_COND(ERROR_NEGATIVE_SEEK, invalid_argument);
|
||||
MAP_ERR_TO_COND(ERROR_NOACCESS, permission_denied);
|
||||
MAP_ERR_TO_COND(ERROR_NOT_ENOUGH_MEMORY, not_enough_memory);
|
||||
MAP_ERR_TO_COND(ERROR_NOT_READY, resource_unavailable_try_again);
|
||||
MAP_ERR_TO_COND(ERROR_OPEN_FAILED, io_error);
|
||||
MAP_ERR_TO_COND(ERROR_OPEN_FILES, device_or_resource_busy);
|
||||
MAP_ERR_TO_COND(ERROR_OUTOFMEMORY, not_enough_memory);
|
||||
MAP_ERR_TO_COND(ERROR_PATH_NOT_FOUND, no_such_file_or_directory);
|
||||
MAP_ERR_TO_COND(ERROR_BAD_NETPATH, no_such_file_or_directory);
|
||||
MAP_ERR_TO_COND(ERROR_READ_FAULT, io_error);
|
||||
MAP_ERR_TO_COND(ERROR_RETRY, resource_unavailable_try_again);
|
||||
MAP_ERR_TO_COND(ERROR_SEEK, io_error);
|
||||
MAP_ERR_TO_COND(ERROR_SHARING_VIOLATION, permission_denied);
|
||||
MAP_ERR_TO_COND(ERROR_TOO_MANY_OPEN_FILES, too_many_files_open);
|
||||
MAP_ERR_TO_COND(ERROR_WRITE_FAULT, io_error);
|
||||
MAP_ERR_TO_COND(ERROR_WRITE_PROTECT, permission_denied);
|
||||
MAP_ERR_TO_COND(WSAEACCES, permission_denied);
|
||||
MAP_ERR_TO_COND(WSAEBADF, bad_file_descriptor);
|
||||
MAP_ERR_TO_COND(WSAEFAULT, bad_address);
|
||||
MAP_ERR_TO_COND(WSAEINTR, interrupted);
|
||||
MAP_ERR_TO_COND(WSAEINVAL, invalid_argument);
|
||||
MAP_ERR_TO_COND(WSAEMFILE, too_many_files_open);
|
||||
MAP_ERR_TO_COND(WSAENAMETOOLONG, filename_too_long);
|
||||
default:
|
||||
return std::error_code(windows_error_code, std::system_category());
|
||||
}
|
||||
#undef MAP_ERR_TO_COND
|
||||
}
|
||||
|
||||
static std::error_code rename_internal(HANDLE from_handle, const std::wstring &wide_to, bool replace_if_exists)
|
||||
{
|
||||
std::vector<char> rename_info_buf(sizeof(FILE_RENAME_INFO) - sizeof(wchar_t) + (wide_to.size() * sizeof(wchar_t)));
|
||||
FILE_RENAME_INFO &rename_info = *reinterpret_cast<FILE_RENAME_INFO*>(rename_info_buf.data());
|
||||
rename_info.ReplaceIfExists = replace_if_exists;
|
||||
rename_info.RootDirectory = 0;
|
||||
rename_info.FileNameLength = DWORD(wide_to.size() * sizeof(wchar_t));
|
||||
std::copy(wide_to.begin(), wide_to.end(), &rename_info.FileName[0]);
|
||||
|
||||
::SetLastError(ERROR_SUCCESS);
|
||||
if (! ::SetFileInformationByHandle(from_handle, FileRenameInfo, &rename_info, (DWORD)rename_info_buf.size())) {
|
||||
unsigned Error = GetLastError();
|
||||
if (Error == ERROR_SUCCESS)
|
||||
Error = ERROR_CALL_NOT_IMPLEMENTED; // Wine doesn't always set error code.
|
||||
return map_windows_error(Error);
|
||||
}
|
||||
|
||||
return std::error_code();
|
||||
}
|
||||
|
||||
static std::error_code real_path_from_handle(HANDLE H, std::wstring &buffer)
|
||||
{
|
||||
buffer.resize(MAX_PATH + 1);
|
||||
DWORD CountChars = ::GetFinalPathNameByHandleW(H, (LPWSTR)buffer.data(), (DWORD)buffer.size() - 1, FILE_NAME_NORMALIZED);
|
||||
if (CountChars > buffer.size()) {
|
||||
// The buffer wasn't big enough, try again. In this case the return value *does* indicate the size of the null terminator.
|
||||
buffer.resize((size_t)CountChars);
|
||||
CountChars = ::GetFinalPathNameByHandleW(H, (LPWSTR)buffer.data(), (DWORD)buffer.size() - 1, FILE_NAME_NORMALIZED);
|
||||
}
|
||||
if (CountChars == 0)
|
||||
return map_windows_error(GetLastError());
|
||||
buffer.resize(CountChars);
|
||||
return std::error_code();
|
||||
}
|
||||
|
||||
std::error_code rename(const std::string &from, const std::string &to)
|
||||
{
|
||||
// Convert to utf-16.
|
||||
std::wstring wide_from = boost::nowide::widen(from);
|
||||
std::wstring wide_to = boost::nowide::widen(to);
|
||||
|
||||
ScopedFileHandle from_handle;
|
||||
// Retry this a few times to defeat badly behaved file system scanners.
|
||||
for (unsigned retry = 0; retry != 200; ++ retry) {
|
||||
if (retry != 0)
|
||||
::Sleep(10);
|
||||
from_handle = ::CreateFileW((LPWSTR)wide_from.data(), GENERIC_READ | DELETE, FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL);
|
||||
if (from_handle)
|
||||
break;
|
||||
}
|
||||
if (! from_handle)
|
||||
return map_windows_error(GetLastError());
|
||||
|
||||
// We normally expect this loop to succeed after a few iterations. If it
|
||||
// requires more than 200 tries, it's more likely that the failures are due to
|
||||
// a true error, so stop trying.
|
||||
for (unsigned retry = 0; retry != 200; ++ retry) {
|
||||
auto errcode = rename_internal(from_handle, wide_to, true);
|
||||
|
||||
if (errcode == std::error_code(ERROR_CALL_NOT_IMPLEMENTED, std::system_category())) {
|
||||
// Wine doesn't support SetFileInformationByHandle in rename_internal.
|
||||
// Fall back to MoveFileEx.
|
||||
if (std::error_code errcode2 = real_path_from_handle(from_handle, wide_from))
|
||||
return errcode2;
|
||||
if (::MoveFileExW((LPCWSTR)wide_from.data(), (LPCWSTR)wide_to.data(), MOVEFILE_REPLACE_EXISTING))
|
||||
return std::error_code();
|
||||
return map_windows_error(GetLastError());
|
||||
}
|
||||
|
||||
if (! errcode || errcode != std::errc::permission_denied)
|
||||
return errcode;
|
||||
|
||||
// The destination file probably exists and is currently open in another
|
||||
// process, either because the file was opened without FILE_SHARE_DELETE or
|
||||
// it is mapped into memory (e.g. using MemoryBuffer). Rename it in order to
|
||||
// move it out of the way of the source file. Use FILE_FLAG_DELETE_ON_CLOSE
|
||||
// to arrange for the destination file to be deleted when the other process
|
||||
// closes it.
|
||||
ScopedFileHandle to_handle(::CreateFileW((LPCWSTR)wide_to.data(), GENERIC_READ | DELETE, FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL | FILE_FLAG_DELETE_ON_CLOSE, NULL));
|
||||
if (! to_handle) {
|
||||
auto errcode = map_windows_error(GetLastError());
|
||||
// Another process might have raced with us and moved the existing file
|
||||
// out of the way before we had a chance to open it. If that happens, try
|
||||
// to rename the source file again.
|
||||
if (errcode == std::errc::no_such_file_or_directory)
|
||||
continue;
|
||||
return errcode;
|
||||
}
|
||||
|
||||
BY_HANDLE_FILE_INFORMATION FI;
|
||||
if (! ::GetFileInformationByHandle(to_handle, &FI))
|
||||
return map_windows_error(GetLastError());
|
||||
|
||||
// Try to find a unique new name for the destination file.
|
||||
for (unsigned unique_id = 0; unique_id != 200; ++ unique_id) {
|
||||
std::wstring tmp_filename = wide_to + L".tmp" + std::to_wstring(unique_id);
|
||||
std::error_code errcode = rename_internal(to_handle, tmp_filename, false);
|
||||
if (errcode) {
|
||||
if (errcode == std::make_error_code(std::errc::file_exists) || errcode == std::make_error_code(std::errc::permission_denied)) {
|
||||
// Again, another process might have raced with us and moved the file
|
||||
// before we could move it. Check whether this is the case, as it
|
||||
// might have caused the permission denied error. If that was the
|
||||
// case, we don't need to move it ourselves.
|
||||
ScopedFileHandle to_handle2(::CreateFileW((LPCWSTR)wide_to.data(), 0, FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL));
|
||||
if (! to_handle2) {
|
||||
auto errcode = map_windows_error(GetLastError());
|
||||
if (errcode == std::errc::no_such_file_or_directory)
|
||||
break;
|
||||
return errcode;
|
||||
}
|
||||
BY_HANDLE_FILE_INFORMATION FI2;
|
||||
if (! ::GetFileInformationByHandle(to_handle2, &FI2))
|
||||
return map_windows_error(GetLastError());
|
||||
if (FI.nFileIndexHigh != FI2.nFileIndexHigh || FI.nFileIndexLow != FI2.nFileIndexLow || FI.dwVolumeSerialNumber != FI2.dwVolumeSerialNumber)
|
||||
break;
|
||||
continue;
|
||||
}
|
||||
return errcode;
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
// Okay, the old destination file has probably been moved out of the way at
|
||||
// this point, so try to rename the source file again. Still, another
|
||||
// process might have raced with us to create and open the destination
|
||||
// file, so we need to keep doing this until we succeed.
|
||||
}
|
||||
|
||||
// The most likely root cause.
|
||||
return std::make_error_code(std::errc::permission_denied);
|
||||
}
|
||||
} // namespace WindowsSupport
|
||||
#endif /* _WIN32 */
|
||||
|
||||
// borrowed from LVVM lib/Support/Windows/Path.inc
|
||||
int rename_file(const std::string &from, const std::string &to)
|
||||
std::error_code rename_file(const std::string &from, const std::string &to)
|
||||
{
|
||||
int ec = 0;
|
||||
|
||||
#ifdef _WIN32
|
||||
|
||||
// Convert to utf-16.
|
||||
std::wstring wide_from = boost::nowide::widen(from);
|
||||
std::wstring wide_to = boost::nowide::widen(to);
|
||||
|
||||
// Retry while we see recoverable errors.
|
||||
// System scanners (eg. indexer) might open the source file when it is written
|
||||
// and closed.
|
||||
bool TryReplace = true;
|
||||
|
||||
// This loop may take more than 2000 x 1ms to finish.
|
||||
for (int i = 0; i < 2000; ++ i) {
|
||||
if (i > 0)
|
||||
// Sleep 1ms
|
||||
::Sleep(1);
|
||||
if (TryReplace) {
|
||||
// Try ReplaceFile first, as it is able to associate a new data stream
|
||||
// with the destination even if the destination file is currently open.
|
||||
if (::ReplaceFileW(wide_to.data(), wide_from.data(), NULL, 0, NULL, NULL))
|
||||
return 0;
|
||||
DWORD ReplaceError = ::GetLastError();
|
||||
ec = -1; // ReplaceError
|
||||
// If ReplaceFileW returned ERROR_UNABLE_TO_MOVE_REPLACEMENT or
|
||||
// ERROR_UNABLE_TO_MOVE_REPLACEMENT_2, retry but only use MoveFileExW().
|
||||
if (ReplaceError == ERROR_UNABLE_TO_MOVE_REPLACEMENT ||
|
||||
ReplaceError == ERROR_UNABLE_TO_MOVE_REPLACEMENT_2) {
|
||||
TryReplace = false;
|
||||
continue;
|
||||
}
|
||||
// If ReplaceFileW returned ERROR_UNABLE_TO_REMOVE_REPLACED, retry
|
||||
// using ReplaceFileW().
|
||||
if (ReplaceError == ERROR_UNABLE_TO_REMOVE_REPLACED)
|
||||
continue;
|
||||
// We get ERROR_FILE_NOT_FOUND if the destination file is missing.
|
||||
// MoveFileEx can handle this case.
|
||||
if (ReplaceError != ERROR_ACCESS_DENIED && ReplaceError != ERROR_FILE_NOT_FOUND && ReplaceError != ERROR_SHARING_VIOLATION)
|
||||
break;
|
||||
}
|
||||
if (::MoveFileExW(wide_from.c_str(), wide_to.c_str(), MOVEFILE_COPY_ALLOWED | MOVEFILE_REPLACE_EXISTING))
|
||||
return 0;
|
||||
DWORD MoveError = ::GetLastError();
|
||||
ec = -1; // MoveError
|
||||
if (MoveError != ERROR_ACCESS_DENIED && MoveError != ERROR_SHARING_VIOLATION)
|
||||
break;
|
||||
}
|
||||
|
||||
return WindowsSupport::rename(from, to);
|
||||
#else
|
||||
|
||||
boost::nowide::remove(to.c_str());
|
||||
ec = boost::nowide::rename(from.c_str(), to.c_str());
|
||||
|
||||
return std::make_error_code(static_cast<std::errc>(boost::nowide::rename(from.c_str(), to.c_str())));
|
||||
#endif
|
||||
|
||||
return ec;
|
||||
}
|
||||
|
||||
int copy_file(const std::string &from, const std::string &to)
|
||||
|
|
|
@ -98,9 +98,10 @@ void AppConfig::load()
|
|||
pt::read_ini(ifs, tree);
|
||||
} catch (pt::ptree_error& ex) {
|
||||
// Error while parsing config file. We'll customize the error message and rethrow to be displayed.
|
||||
throw std::runtime_error(wxString::Format(_(L("Error parsing config file, it is probably corrupted. "
|
||||
"Try to manualy delete the file. Your user profiles will not be affected.\n\n%s\n\n%s")),
|
||||
AppConfig::config_path(), ex.what()).ToStdString());
|
||||
throw std::runtime_error(
|
||||
_utf8(L("Error parsing PrusaSlicer config file, it is probably corrupted. "
|
||||
"Try to manualy delete the file to recover from the error. Your user profiles will not be affected.")) +
|
||||
"\n\n" + AppConfig::config_path() + "\n\n" + ex.what());
|
||||
}
|
||||
|
||||
// 2) Parse the property_tree, extract the sections and key / value pairs.
|
||||
|
|
Loading…
Reference in a new issue