From 01406fd52182d72cf1789174dd64ee4284e9f2cc Mon Sep 17 00:00:00 2001 From: Vojtech Bubnik Date: Mon, 15 Mar 2021 16:19:22 +0100 Subject: [PATCH] Worked around some quirky Linux file system issues. Namely the Chromebooks share their file system to Linux using the 9p file system, which does not support setting file ownership. Newly PrusaSlicer will detect platform and it will not panick if copy_file() cannot set file ownership after copying. It just logs the incident, and on chromebooks the loglevel for that incident is "Info", not "Error". Adjusted the full screen mode to contain menu bar. Moved Platform.cpp/hpp to libslic3r --- src/PrusaSlicer.cpp | 3 + src/libslic3r/CMakeLists.txt | 2 + src/{slic3r/Utils => libslic3r}/Platform.cpp | 8 - src/{slic3r/Utils => libslic3r}/Platform.hpp | 9 +- src/libslic3r/utils.cpp | 148 ++++++++++++++++++- src/slic3r/CMakeLists.txt | 2 - src/slic3r/GUI/GUI_Init.cpp | 3 - src/slic3r/GUI/MainFrame.cpp | 6 +- src/slic3r/GUI/OpenGLManager.cpp | 5 +- src/slic3r/GUI/Plater.cpp | 5 +- src/slic3r/GUI/PresetHints.cpp | 1 - src/slic3r/GUI/RemovableDriveManager.cpp | 6 +- 12 files changed, 168 insertions(+), 30 deletions(-) rename src/{slic3r/Utils => libslic3r}/Platform.cpp (92%) rename src/{slic3r/Utils => libslic3r}/Platform.hpp (80%) diff --git a/src/PrusaSlicer.cpp b/src/PrusaSlicer.cpp index ed2d70272..10da1cb07 100644 --- a/src/PrusaSlicer.cpp +++ b/src/PrusaSlicer.cpp @@ -38,6 +38,7 @@ #include "libslic3r/GCode/PostProcessor.hpp" #include "libslic3r/Model.hpp" #include "libslic3r/ModelArrange.hpp" +#include "libslic3r/Platform.hpp" #include "libslic3r/Print.hpp" #include "libslic3r/SLAPrint.hpp" #include "libslic3r/TriangleMesh.hpp" @@ -68,6 +69,8 @@ int CLI::run(int argc, char **argv) { // Mark the main thread for the debugger and for runtime checks. set_current_thread_name("slic3r_main"); + // Detect the operating system flavor. + detect_platform(); #ifdef __WXGTK__ // On Linux, wxGTK has no support for Wayland, and the app crashes on diff --git a/src/libslic3r/CMakeLists.txt b/src/libslic3r/CMakeLists.txt index 6159b2c5c..4a762f7e1 100644 --- a/src/libslic3r/CMakeLists.txt +++ b/src/libslic3r/CMakeLists.txt @@ -141,6 +141,8 @@ add_library(libslic3r STATIC PerimeterGenerator.hpp PlaceholderParser.cpp PlaceholderParser.hpp + Platform.cpp + Platform.hpp Point.cpp Point.hpp Polygon.cpp diff --git a/src/slic3r/Utils/Platform.cpp b/src/libslic3r/Platform.cpp similarity index 92% rename from src/slic3r/Utils/Platform.cpp rename to src/libslic3r/Platform.cpp index 3d101d662..4c8805149 100644 --- a/src/slic3r/Utils/Platform.cpp +++ b/src/libslic3r/Platform.cpp @@ -1,15 +1,8 @@ #include "Platform.hpp" - -// For starting another PrusaSlicer instance on OSX. -// Fails to compile on Windows on the build server. - -#include - #include namespace Slic3r { -namespace GUI { static auto s_platform = Platform::Uninitialized; static auto s_platform_flavor = PlatformFlavor::Uninitialized; @@ -74,5 +67,4 @@ PlatformFlavor platform_flavor() return s_platform_flavor; } -} // namespace GUI } // namespace Slic3r diff --git a/src/slic3r/Utils/Platform.hpp b/src/libslic3r/Platform.hpp similarity index 80% rename from src/slic3r/Utils/Platform.hpp rename to src/libslic3r/Platform.hpp index c0ee3541d..735728e89 100644 --- a/src/slic3r/Utils/Platform.hpp +++ b/src/libslic3r/Platform.hpp @@ -1,8 +1,7 @@ -#ifndef SLIC3R_GUI_Utils_Platform_HPP -#define SLIC3R_GUI_Utils_Platform_HPP +#ifndef SLIC3R_Platform_HPP +#define SLIC3R_Platform_HPP namespace Slic3r { -namespace GUI { enum class Platform { @@ -37,8 +36,6 @@ void detect_platform(); Platform platform(); PlatformFlavor platform_flavor(); - -} // namespace GUI } // namespace Slic3r -#endif // SLIC3R_GUI_Utils_Platform_HPP +#endif // SLIC3R_Platform_HPP diff --git a/src/libslic3r/utils.cpp b/src/libslic3r/utils.cpp index 0c26d42c8..1ac45f1b5 100644 --- a/src/libslic3r/utils.cpp +++ b/src/libslic3r/utils.cpp @@ -6,6 +6,7 @@ #include #include +#include "Platform.hpp" #include "Time.hpp" #ifdef WIN32 @@ -19,9 +20,14 @@ #ifdef BSD #include #endif - #ifdef __APPLE__ + #ifdef __APPLE__ #include #endif + #ifdef __linux__ + #include + #include + #include + #endif #endif #include @@ -417,6 +423,140 @@ std::error_code rename_file(const std::string &from, const std::string &to) #endif } +#ifdef __linux__ +// Copied from boost::filesystem, to support copying a file to a weird filesystem, which does not support changing file attributes, +// for example ChromeOS Linux integration or FlashAIR WebDAV. +// Copied and simplified from boost::filesystem::detail::copy_file() with option = overwrite_if_exists and with just the Linux path kept, +// and only features supported by Linux 3.10 (on our build server with CentOS 7) are kept, namely sendfile with ranges and statx() are not supported. +bool copy_file_linux(const boost::filesystem::path &from, const boost::filesystem::path &to, boost::system::error_code &ec) +{ + using namespace boost::filesystem; + + struct fd_wrapper + { + int fd { -1 }; + fd_wrapper() = default; + explicit fd_wrapper(int fd) throw() : fd(fd) {} + ~fd_wrapper() throw() { if (fd >= 0) ::close(fd); } + }; + + ec.clear(); + int err = 0; + + // Note: Declare fd_wrappers here so that errno is not clobbered by close() that may be called in fd_wrapper destructors + fd_wrapper infile, outfile; + + while (true) { + infile.fd = ::open(from.c_str(), O_RDONLY | O_CLOEXEC); + if (infile.fd < 0) { + err = errno; + if (err == EINTR) + continue; + fail: + ec.assign(err, boost::system::system_category()); + return false; + } + break; + } + + struct ::stat from_stat; + if (::fstat(infile.fd, &from_stat) != 0) { + fail_errno: + err = errno; + goto fail; + } + + const mode_t from_mode = from_stat.st_mode; + if (!S_ISREG(from_mode)) { + err = ENOSYS; + goto fail; + } + + // Enable writing for the newly created files. Having write permission set is important e.g. for NFS, + // which checks the file permission on the server, even if the client's file descriptor supports writing. + mode_t to_mode = from_mode | S_IWUSR; + int oflag = O_WRONLY | O_CLOEXEC | O_CREAT | O_TRUNC; + + while (true) { + outfile.fd = ::open(to.c_str(), oflag, to_mode); + if (outfile.fd < 0) { + err = errno; + if (err == EINTR) + continue; + goto fail; + } + break; + } + + struct ::stat to_stat; + if (::fstat(outfile.fd, &to_stat) != 0) + goto fail_errno; + + to_mode = to_stat.st_mode; + if (!S_ISREG(to_mode)) { + err = ENOSYS; + goto fail; + } + + if (from_stat.st_dev == to_stat.st_dev && from_stat.st_ino == to_stat.st_ino) { + err = EEXIST; + goto fail; + } + + //! copy_file implementation that uses sendfile loop. Requires sendfile to support file descriptors. + //FIXME Vojtech: This is a copy loop valid for Linux 2.6.33 and newer. + // copy_file_data_copy_file_range() supports cross-filesystem copying since 5.3, but Vojtech did not want to polute this + // function with that, we don't think the performance gain is worth it for the types of files we are copying, + // and our build server based on CentOS 7 with Linux 3.10 does not support that anyways. + { + // sendfile will not send more than this amount of data in one call + constexpr std::size_t max_send_size = 0x7ffff000u; + uintmax_t offset = 0u; + while (off_t(offset) < from_stat.st_size) { + uintmax_t size_left = from_stat.st_size - offset; + std::size_t size_to_copy = max_send_size; + if (size_left < static_cast(max_send_size)) + size_to_copy = static_cast(size_left); + ssize_t sz = ::sendfile(outfile.fd, infile.fd, nullptr, size_to_copy); + if (sz < 0) { + err = errno; + if (err == EINTR) + continue; + if (err == 0) + break; + goto fail; // err already contains the error code + } + offset += sz; + } + } + + // If we created a new file with an explicitly added S_IWUSR permission, + // we may need to update its mode bits to match the source file. + if (to_mode != from_mode && ::fchmod(outfile.fd, from_mode) != 0) { + if (platform_flavor() == PlatformFlavor::LinuxOnChromium) { + // Ignore that. 9p filesystem does not allow fmod(). + BOOST_LOG_TRIVIAL(info) << "copy_file_linux() failed to fchmod() the output file \"" << to.string() << "\" to " << from_mode << ": " << ec.message() << + " This may be expected when writing to a 9p filesystem."; + } else { + // Generic linux. Write out an error to console. At least we may get some feedback. + BOOST_LOG_TRIVIAL(error) << "copy_file_linux() failed to fchmod() the output file \"" << to.string() << "\" to " << from_mode << ": " << ec.message(); + } + } + + // Note: Use fsync/fdatasync followed by close to avoid dealing with the possibility of close failing with EINTR. + // Even if close fails, including with EINTR, most operating systems (presumably, except HP-UX) will close the + // file descriptor upon its return. This means that if an error happens later, when the OS flushes data to the + // underlying media, this error will go unnoticed and we have no way to receive it from close. Calling fsync/fdatasync + // ensures that all data have been written, and even if close fails for some unfathomable reason, we don't really + // care at that point. + err = ::fdatasync(outfile.fd); + if (err != 0) + goto fail_errno; + + return true; +} +#endif // __linux__ + CopyFileResult copy_file_inner(const std::string& from, const std::string& to, std::string& error_message) { const boost::filesystem::path source(from); @@ -434,7 +574,13 @@ CopyFileResult copy_file_inner(const std::string& from, const std::string& to, s if (ec) BOOST_LOG_TRIVIAL(debug) << "boost::filesystem::permisions before copy error message (this could be irrelevant message based on file system): " << ec.message(); ec.clear(); +#ifdef __linux__ + // We want to allow copying files on Linux to succeed even if changing the file attributes fails. + // That may happen when copying on some exotic file system, for example Linux on Chrome. + copy_file_linux(source, target, ec); +#else // __linux__ boost::filesystem::copy_file(source, target, boost::filesystem::copy_option::overwrite_if_exists, ec); +#endif // __linux__ if (ec) { error_message = ec.message(); return FAIL_COPY_FILE; diff --git a/src/slic3r/CMakeLists.txt b/src/slic3r/CMakeLists.txt index 6436de2ca..4b3a1c6ca 100644 --- a/src/slic3r/CMakeLists.txt +++ b/src/slic3r/CMakeLists.txt @@ -209,8 +209,6 @@ set(SLIC3R_GUI_SOURCES Utils/Bonjour.hpp Utils/PresetUpdater.cpp Utils/PresetUpdater.hpp - Utils/Platform.cpp - Utils/Platform.hpp Utils/Process.cpp Utils/Process.hpp Utils/Profile.hpp diff --git a/src/slic3r/GUI/GUI_Init.cpp b/src/slic3r/GUI/GUI_Init.cpp index 3c77f3335..839782741 100644 --- a/src/slic3r/GUI/GUI_Init.cpp +++ b/src/slic3r/GUI/GUI_Init.cpp @@ -9,7 +9,6 @@ #include "slic3r/GUI/format.hpp" #include "slic3r/GUI/MainFrame.hpp" #include "slic3r/GUI/Plater.hpp" -#include "slic3r/Utils/Platform.hpp" // To show a message box if GUI initialization ends up with an exception thrown. #include @@ -37,8 +36,6 @@ int GUI_Run(GUI_InitParams ¶ms) signal(SIGCHLD, SIG_DFL); #endif // __APPLE__ - detect_platform(); - try { GUI::GUI_App* gui = new GUI::GUI_App(params.start_as_gcodeviewer ? GUI::GUI_App::EAppMode::GCodeViewer : GUI::GUI_App::EAppMode::Editor); if (gui->get_app_mode() != GUI::GUI_App::EAppMode::GCodeViewer) { diff --git a/src/slic3r/GUI/MainFrame.cpp b/src/slic3r/GUI/MainFrame.cpp index f50c7e356..ad4468683 100644 --- a/src/slic3r/GUI/MainFrame.cpp +++ b/src/slic3r/GUI/MainFrame.cpp @@ -1218,8 +1218,10 @@ void MainFrame::init_menubar_as_editor() [this](wxCommandEvent&) { m_plater->collapse_sidebar(!m_plater->is_sidebar_collapsed()); }, this, []() { return true; }, [this]() { return m_plater->is_sidebar_collapsed(); }, this); append_menu_check_item(viewMenu, wxID_ANY, _L("&Full screen") + "\t" + "F11", _L("Full screen"), - [this](wxCommandEvent&) { this->ShowFullScreen(!this->IsFullScreen()); }, this, - []() { return true; }, [this]() { return this->IsFullScreen(); }, this); + [this](wxCommandEvent&) { this->ShowFullScreen(!this->IsFullScreen(), + // wxFULLSCREEN_ALL: wxFULLSCREEN_NOMENUBAR | wxFULLSCREEN_NOTOOLBAR | wxFULLSCREEN_NOSTATUSBAR | wxFULLSCREEN_NOBORDER | wxFULLSCREEN_NOCAPTION + wxFULLSCREEN_NOSTATUSBAR | wxFULLSCREEN_NOBORDER | wxFULLSCREEN_NOCAPTION); }, + this, []() { return true; }, [this]() { return this->IsFullScreen(); }, this); } // Help menu diff --git a/src/slic3r/GUI/OpenGLManager.cpp b/src/slic3r/GUI/OpenGLManager.cpp index 8e8774036..c843da460 100644 --- a/src/slic3r/GUI/OpenGLManager.cpp +++ b/src/slic3r/GUI/OpenGLManager.cpp @@ -4,7 +4,8 @@ #include "GUI.hpp" #include "I18N.hpp" #include "3DScene.hpp" -#include "slic3r/Utils/Platform.hpp" + +#include "libslic3r/Platform.hpp" #include @@ -324,7 +325,7 @@ void OpenGLManager::detect_multisample(int* attribList) enable_multisample && // Disable multi-sampling on ChromeOS, as the OpenGL virtualization swaps Red/Blue channels with multi-sampling enabled, // at least on some platforms. - (platform() != Platform::Linux || platform_flavor() != PlatformFlavor::LinuxOnChromium) && + platform_flavor() != PlatformFlavor::LinuxOnChromium && wxGLCanvas::IsDisplaySupported(attribList) ? EMultisampleState::Enabled : EMultisampleState::Disabled; // Alternative method: it was working on previous version of wxWidgets but not with the latest, at least on Windows diff --git a/src/slic3r/GUI/Plater.cpp b/src/slic3r/GUI/Plater.cpp index 15e89168a..273f39b1e 100644 --- a/src/slic3r/GUI/Plater.cpp +++ b/src/slic3r/GUI/Plater.cpp @@ -76,7 +76,6 @@ #include "../Utils/FixModelByWin10.hpp" #include "../Utils/UndoRedo.hpp" #include "../Utils/PresetUpdater.hpp" -#include "../Utils/Platform.hpp" #include "../Utils/Process.hpp" #include "RemovableDriveManager.hpp" #include "InstanceCheck.hpp" @@ -89,7 +88,9 @@ #include // Needs to be last because reasons :-/ #include "WipeTowerDialog.hpp" + #include "libslic3r/CustomGCode.hpp" +#include "libslic3r/Platform.hpp" using boost::optional; namespace fs = boost::filesystem; @@ -3660,7 +3661,7 @@ void Plater::priv::on_process_completed(SlicingProcessCompletedEvent &evt) show_action_buttons(false); notification_manager->push_exporting_finished_notification(last_output_path, last_output_dir_path, // Don't offer the "Eject" button on ChromeOS, the Linux side has no control over it. - platform() != Platform::Linux || platform_flavor() != PlatformFlavor::LinuxOnChromium); + platform_flavor() != PlatformFlavor::LinuxOnChromium); wxGetApp().removable_drive_manager()->set_exporting_finished(true); }else if (exporting_status == ExportingStatus::EXPORTING_TO_LOCAL && !has_error) notification_manager->push_exporting_finished_notification(last_output_path, last_output_dir_path, false); diff --git a/src/slic3r/GUI/PresetHints.cpp b/src/slic3r/GUI/PresetHints.cpp index 0e5e10d45..181dcfda4 100644 --- a/src/slic3r/GUI/PresetHints.cpp +++ b/src/slic3r/GUI/PresetHints.cpp @@ -139,7 +139,6 @@ std::string PresetHints::maximum_volumetric_flow_description(const PresetBundle const ConfigOptionFloatOrPercent *first_layer_extrusion_width_ptr = (first_layer && first_layer_extrusion_width.value > 0) ? &first_layer_extrusion_width : nullptr; const float lh = float(first_layer ? first_layer_height : layer_height); - const float bfr = bridging ? bridge_flow_ratio : 0.f; double max_flow = 0.; std::string max_flow_extrusion_type; auto limit_by_first_layer_speed = [&first_layer_speed, first_layer](double speed_normal, double speed_max) { diff --git a/src/slic3r/GUI/RemovableDriveManager.cpp b/src/slic3r/GUI/RemovableDriveManager.cpp index c200956e1..33e61ab90 100644 --- a/src/slic3r/GUI/RemovableDriveManager.cpp +++ b/src/slic3r/GUI/RemovableDriveManager.cpp @@ -1,5 +1,5 @@ #include "RemovableDriveManager.hpp" -#include "slic3r/Utils/Platform.hpp" +#include "libslic3r/Platform.hpp" #include #include @@ -232,7 +232,7 @@ std::vector RemovableDriveManager::search_for_removable_drives() cons #else - if (platform() == Platform::Linux && platform_flavor() == PlatformFlavor::LinuxOnChromium) { + if (platform_flavor() == PlatformFlavor::LinuxOnChromium) { // ChromeOS specific: search /mnt/chromeos/removable/* folder search_for_drives_internal::search_path("/mnt/chromeos/removable/*", "/mnt/chromeos/removable", current_drives); } else { @@ -452,7 +452,7 @@ RemovableDriveManager::RemovableDrivesStatus RemovableDriveManager::status() tbb::mutex::scoped_lock lock(m_drives_mutex); out.has_eject = // Cannot control eject on Chromium. - (platform() != Platform::Linux || platform_flavor() != PlatformFlavor::LinuxOnChromium) && + platform_flavor() != PlatformFlavor::LinuxOnChromium && this->find_last_save_path_drive_data() != m_current_drives.end(); out.has_removable_drives = ! m_current_drives.empty(); }