From db324b22951c14d2d3d74b670b6bec4e411c781a Mon Sep 17 00:00:00 2001
From: Vojtech Bubnik <bubnikv@gmail.com>
Date: Tue, 13 Apr 2021 10:55:27 +0200
Subject: [PATCH] Ported ChromeOS support from master aka PrusaSlicer
 2.4.0-alpha: 1) Detect platform 2) Disable OpenGL multi-sampling on ChromeOS
 3) Disable eject on ChromeOS, different location of external devices    mount
 point.

---
 src/PrusaSlicer.cpp                      |   4 +
 src/libslic3r/CMakeLists.txt             |   2 +
 src/libslic3r/Platform.cpp               |  71 ++++++++++++
 src/libslic3r/Platform.hpp               |  41 +++++++
 src/libslic3r/utils.cpp                  | 141 +++++++++++++++++++++++
 src/slic3r/GUI/OpenGLManager.cpp         |  10 +-
 src/slic3r/GUI/Plater.cpp                |   5 +-
 src/slic3r/GUI/RemovableDriveManager.cpp |  47 +++++---
 8 files changed, 303 insertions(+), 18 deletions(-)
 create mode 100644 src/libslic3r/Platform.cpp
 create mode 100644 src/libslic3r/Platform.hpp

diff --git a/src/PrusaSlicer.cpp b/src/PrusaSlicer.cpp
index 60f3a1321..6a019271e 100644
--- a/src/PrusaSlicer.cpp
+++ b/src/PrusaSlicer.cpp
@@ -37,6 +37,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"
@@ -594,6 +595,9 @@ bool CLI::setup(int argc, char **argv)
         }
     }
 
+    // Detect the operating system flavor after SLIC3R_LOGLEVEL is set.
+    detect_platform();
+
     boost::filesystem::path path_to_binary = boost::filesystem::system_complete(argv[0]);
 
     // Path from the Slic3r binary to its resources.
diff --git a/src/libslic3r/CMakeLists.txt b/src/libslic3r/CMakeLists.txt
index 11e37afc6..5e0808190 100644
--- a/src/libslic3r/CMakeLists.txt
+++ b/src/libslic3r/CMakeLists.txt
@@ -137,6 +137,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/libslic3r/Platform.cpp b/src/libslic3r/Platform.cpp
new file mode 100644
index 000000000..ae02c42b3
--- /dev/null
+++ b/src/libslic3r/Platform.cpp
@@ -0,0 +1,71 @@
+#include "Platform.hpp"
+
+#include <boost/log/trivial.hpp>
+#include <boost/filesystem/operations.hpp>
+
+namespace Slic3r {
+
+static auto s_platform 		  = Platform::Uninitialized;
+static auto s_platform_flavor = PlatformFlavor::Uninitialized;
+
+void detect_platform()
+{
+#if defined(_WIN32)
+    BOOST_LOG_TRIVIAL(info) << "Platform: Windows";
+	s_platform 		  = Platform::Windows;
+	s_platform_flavor = PlatformFlavor::Generic;
+#elif defined(__APPLE__)
+    BOOST_LOG_TRIVIAL(info) << "Platform: OSX";
+	s_platform 		  = Platform::OSX;
+	s_platform_flavor = PlatformFlavor::Generic;
+#elif defined(__linux__)
+    BOOST_LOG_TRIVIAL(info) << "Platform: Linux";
+	s_platform 		  = Platform::Linux;
+	s_platform_flavor = PlatformFlavor::GenericLinux;
+	// Test for Chromium.
+	{
+		FILE *f = ::fopen("/proc/version", "rt");
+		if (f) {
+			char buf[4096];
+			// Read the 1st line.
+			if (::fgets(buf, 4096, f)) {
+				if (strstr(buf, "Chromium OS") != nullptr) {
+					s_platform_flavor = PlatformFlavor::LinuxOnChromium;
+				    BOOST_LOG_TRIVIAL(info) << "Platform flavor: LinuxOnChromium";
+				} else if (strstr(buf, "microsoft") != nullptr || strstr(buf, "Microsoft") != nullptr) {
+					if (boost::filesystem::exists("/run/WSL") && getenv("WSL_INTEROP") != nullptr) {
+						BOOST_LOG_TRIVIAL(info) << "Platform flavor: WSL2";
+						s_platform_flavor = PlatformFlavor::WSL2;
+					} else {
+						BOOST_LOG_TRIVIAL(info) << "Platform flavor: WSL";
+						s_platform_flavor = PlatformFlavor::WSL;
+					}
+				}
+			}
+			::fclose(f);
+		}
+	}
+#elif defined(__OpenBSD__)
+    BOOST_LOG_TRIVIAL(info) << "Platform: OpenBSD";
+	s_platform 		  = Platform::BSDUnix;
+	s_platform_flavor = PlatformFlavor::OpenBSD;
+#else
+	// This should not happen.
+    BOOST_LOG_TRIVIAL(info) << "Platform: Unknown";
+	static_assert(false, "Unknown platform detected");
+	s_platform 		  = Platform::Unknown;
+	s_platform_flavor = PlatformFlavor::Unknown;
+#endif
+}
+
+Platform platform()
+{
+	return s_platform;
+}
+
+PlatformFlavor platform_flavor()
+{
+	return s_platform_flavor;
+}
+
+} // namespace Slic3r
diff --git a/src/libslic3r/Platform.hpp b/src/libslic3r/Platform.hpp
new file mode 100644
index 000000000..735728e89
--- /dev/null
+++ b/src/libslic3r/Platform.hpp
@@ -0,0 +1,41 @@
+#ifndef SLIC3R_Platform_HPP
+#define SLIC3R_Platform_HPP
+
+namespace Slic3r {
+
+enum class Platform
+{
+	Uninitialized,
+	Unknown,
+	Windows,
+	OSX,
+	Linux,
+	BSDUnix,
+};
+
+enum class PlatformFlavor
+{
+	Uninitialized,
+	Unknown,
+	// For Windows and OSX, until we need to be more specific.
+	Generic,
+	// For Platform::Linux
+	GenericLinux,
+	LinuxOnChromium,
+	// Microsoft's Windows on Linux (Linux kernel simulated on NTFS kernel)
+	WSL,
+	// Microsoft's Windows on Linux, version 2 (virtual machine)
+	WSL2,
+	// For Platform::BSDUnix
+	OpenBSD,
+};
+
+// To be called on program start-up.
+void 			detect_platform();
+
+Platform 		platform();
+PlatformFlavor 	platform_flavor();
+
+} // namespace Slic3r
+
+#endif // SLIC3R_Platform_HPP
diff --git a/src/libslic3r/utils.cpp b/src/libslic3r/utils.cpp
index 0c26d42c8..9d0162980 100644
--- a/src/libslic3r/utils.cpp
+++ b/src/libslic3r/utils.cpp
@@ -6,6 +6,7 @@
 #include <cstdarg>
 #include <stdio.h>
 
+#include "Platform.hpp"
 #include "Time.hpp"
 
 #ifdef WIN32
@@ -417,6 +418,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<uintmax_t>(max_send_size))
+				size_to_copy = static_cast<std::size_t>(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 +569,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/GUI/OpenGLManager.cpp b/src/slic3r/GUI/OpenGLManager.cpp
index 4f1e00793..1908c1d34 100644
--- a/src/slic3r/GUI/OpenGLManager.cpp
+++ b/src/slic3r/GUI/OpenGLManager.cpp
@@ -5,6 +5,8 @@
 #include "I18N.hpp"
 #include "3DScene.hpp"
 
+#include "libslic3r/Platform.hpp"
+
 #include <GL/glew.h>
 
 #include <boost/algorithm/string/split.hpp>
@@ -333,7 +335,13 @@ void OpenGLManager::detect_multisample(int* attribList)
 {
     int wxVersion = wxMAJOR_VERSION * 10000 + wxMINOR_VERSION * 100 + wxRELEASE_NUMBER;
     bool enable_multisample = wxVersion >= 30003;
-    s_multisample = (enable_multisample && wxGLCanvas::IsDisplaySupported(attribList)) ? EMultisampleState::Enabled : EMultisampleState::Disabled;
+    s_multisample = 
+        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_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
     // s_multisample = enable_multisample && wxGLCanvas::IsExtensionSupported("WGL_ARB_multisample");
 }
diff --git a/src/slic3r/GUI/Plater.cpp b/src/slic3r/GUI/Plater.cpp
index e154c0567..a9e66333e 100644
--- a/src/slic3r/GUI/Plater.cpp
+++ b/src/slic3r/GUI/Plater.cpp
@@ -88,6 +88,7 @@
 #include <wx/glcanvas.h>    // Needs to be last because reasons :-/
 #include "WipeTowerDialog.hpp"
 #include "libslic3r/CustomGCode.hpp"
+#include "libslic3r/Platform.hpp"
 
 using boost::optional;
 namespace fs = boost::filesystem;
@@ -3769,7 +3770,9 @@ void Plater::priv::on_process_completed(SlicingProcessCompletedEvent &evt)
         // If writing to removable drive was scheduled, show notification with eject button
         if (exporting_status == ExportingStatus::EXPORTING_TO_REMOVABLE && !has_error) {
             show_action_buttons(false);
-            notification_manager->push_exporting_finished_notification(last_output_path, last_output_dir_path, true);
+            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_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/RemovableDriveManager.cpp b/src/slic3r/GUI/RemovableDriveManager.cpp
index b11cc8dd5..59a202beb 100644
--- a/src/slic3r/GUI/RemovableDriveManager.cpp
+++ b/src/slic3r/GUI/RemovableDriveManager.cpp
@@ -1,4 +1,5 @@
 #include "RemovableDriveManager.hpp"
+#include "libslic3r/Platform.hpp"
 #include <libslic3r/libslic3r.h>
 
 #include <boost/nowide/convert.hpp>
@@ -185,8 +186,13 @@ namespace search_for_drives_internal
 	{
 		//confirms if the file is removable drive and adds it to vector
 
-		//if not same file system - could be removable drive
-		if (! compare_filesystem_id(path, parent_path)) {
+		if (
+#ifdef __linux__
+			// Chromium mounts removable drives in a way that produces the same device ID.
+			platform_flavor() == PlatformFlavor::LinuxOnChromium ||
+#endif
+			// If not same file system - could be removable drive.
+			! compare_filesystem_id(path, parent_path)) {
 			//free space
 			boost::system::error_code ec;
 			boost::filesystem::space_info si = boost::filesystem::space(path, ec);
@@ -229,22 +235,28 @@ std::vector<DriveData> RemovableDriveManager::search_for_removable_drives() cons
 
 #else
 
-    //search /media/* folder
-	search_for_drives_internal::search_path("/media/*", "/media", current_drives);
+	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 {
+	    //search /media/* folder
+		search_for_drives_internal::search_path("/media/*", "/media", current_drives);
 
-	//search_path("/Volumes/*", "/Volumes");
-    std::string path(std::getenv("USER"));
-	std::string pp(path);
+		//search_path("/Volumes/*", "/Volumes");
+	    std::string path(std::getenv("USER"));
+		std::string pp(path);
 
-	//search /media/USERNAME/* folder
-	pp = "/media/"+pp;
-	path = "/media/" + path + "/*";
-	search_for_drives_internal::search_path(path, pp, current_drives);
+		//search /media/USERNAME/* folder
+		pp = "/media/"+pp;
+		path = "/media/" + path + "/*";
+		search_for_drives_internal::search_path(path, pp, current_drives);
+
+		//search /run/media/USERNAME/* folder
+		path = "/run" + path;
+		pp = "/run"+pp;
+		search_for_drives_internal::search_path(path, pp, current_drives);
+	}
 
-	//search /run/media/USERNAME/* folder
-	path = "/run" + path;
-	pp = "/run"+pp;
-	search_for_drives_internal::search_path(path, pp, current_drives);
 #endif
 
 	return current_drives;
@@ -441,7 +453,10 @@ RemovableDriveManager::RemovableDrivesStatus RemovableDriveManager::status()
 	RemovableDriveManager::RemovableDrivesStatus out;
 	{
 		tbb::mutex::scoped_lock lock(m_drives_mutex);
-		out.has_eject = this->find_last_save_path_drive_data() != m_current_drives.end();
+		out.has_eject = 
+			// Cannot control eject on Chromium.
+			platform_flavor() != PlatformFlavor::LinuxOnChromium &&
+			this->find_last_save_path_drive_data() != m_current_drives.end();
 		out.has_removable_drives = ! m_current_drives.empty();
 	}
 	if (! out.has_eject)