From 2860353b9f30aee8129f43648899d8a6120fca84 Mon Sep 17 00:00:00 2001 From: raistlin7447 Date: Sun, 5 Jul 2026 20:18:04 -0500 Subject: [PATCH] fix: multi-user slicing crash on shared temp dir (#14607) On Linux every account shares /tmp, but slicing builds temp paths there under fixed, app-owned names via temporary_dir() (model backups, STEP import, part-skip). The first user to slice creates and owns those dirs, so the next user cannot write under them and slicing crashes with "No such file or directory". Tag the app temp root with the user id at startup (/orcaslicer_) so every temporary_dir() consumer is isolated at once. The id stays at the top level of the world-writable system temp so each user's dir is created directly there; a shared parent dir would be owned by whichever user made it first. The root is pre-created because STEP import writes into it directly. Windows keeps the plain temp dir since it is already per-user. Fixes #10108. Same root cause as #5969. --- src/OrcaSlicer.cpp | 8 ++++- src/libslic3r/Utils.hpp | 4 +++ src/libslic3r/utils.cpp | 18 ++++++++++++ tests/libslic3r/CMakeLists.txt | 1 + tests/libslic3r/test_utils.cpp | 54 ++++++++++++++++++++++++++++++++++ 5 files changed, 84 insertions(+), 1 deletion(-) create mode 100644 tests/libslic3r/test_utils.cpp diff --git a/src/OrcaSlicer.cpp b/src/OrcaSlicer.cpp index 53f0352c09..7e1341ac18 100644 --- a/src/OrcaSlicer.cpp +++ b/src/OrcaSlicer.cpp @@ -1317,7 +1317,13 @@ int CLI::run(int argc, char **argv) return CLI_INVALID_PARAMS; } BOOST_LOG_TRIVIAL(info) << "finished setup params, argc="<< argc << std::endl; - std::string temp_path = wxFileName::GetTempDir().utf8_str().data(); + std::string temp_path = per_user_temp_dir(wxFileName::GetTempDir().utf8_str().data(), per_user_temp_id()); + // Some consumers write into the temp root directly, so create it up front. + try { + boost::filesystem::create_directories(temp_path); + } catch (const std::exception &ex) { + BOOST_LOG_TRIVIAL(warning) << "failed to create per-user temp dir " << temp_path << ": " << ex.what(); + } set_temporary_dir(temp_path); m_extra_config.apply(m_config, true); diff --git a/src/libslic3r/Utils.hpp b/src/libslic3r/Utils.hpp index fee9f2e5ac..a76857dc4b 100644 --- a/src/libslic3r/Utils.hpp +++ b/src/libslic3r/Utils.hpp @@ -300,6 +300,10 @@ std::string header_gcodeviewer_generated(); // getpid platform wrapper extern unsigned get_current_pid(); +// Per-user id for isolating temp dirs; empty on Windows (its temp dir is already per-user). +std::string per_user_temp_id(); +// Per-user temp root under `base`; an empty `user_id` returns `base` unchanged. +std::string per_user_temp_dir(const std::string &base, const std::string &user_id); // BBS: backup & restore std::string get_process_name(int pid); diff --git a/src/libslic3r/utils.cpp b/src/libslic3r/utils.cpp index f3edb516ab..131369d212 100644 --- a/src/libslic3r/utils.cpp +++ b/src/libslic3r/utils.cpp @@ -1285,6 +1285,24 @@ unsigned get_current_pid() #endif } +std::string per_user_temp_id() +{ +#ifdef WIN32 + return {}; +#else + return std::to_string(static_cast(::getuid())); +#endif +} + +std::string per_user_temp_dir(const std::string &base, const std::string &user_id) +{ + if (user_id.empty()) + return base; + // Keep the id at the top level so each user's dir sits directly in the world-writable temp + // root; a shared parent dir would be owned by whichever user created it first. + return base + "/orcaslicer_" + user_id; +} + // BBS: backup & restore std::string get_process_name(int pid) { diff --git a/tests/libslic3r/CMakeLists.txt b/tests/libslic3r/CMakeLists.txt index d5e6eb958e..072e1a16b2 100644 --- a/tests/libslic3r/CMakeLists.txt +++ b/tests/libslic3r/CMakeLists.txt @@ -23,6 +23,7 @@ add_executable(${_TEST_NAME}_tests test_stl.cpp test_meshboolean.cpp test_marchingsquares.cpp + test_utils.cpp test_timeutils.cpp test_voronoi.cpp test_optimizers.cpp diff --git a/tests/libslic3r/test_utils.cpp b/tests/libslic3r/test_utils.cpp new file mode 100644 index 0000000000..c039069b2a --- /dev/null +++ b/tests/libslic3r/test_utils.cpp @@ -0,0 +1,54 @@ +#include + +#include "libslic3r/Utils.hpp" + +#ifndef _WIN32 +#include // getuid +#endif + +using namespace Slic3r; + +TEST_CASE("per_user_temp_dir composes a per-user temp root", "[utils]") { + const std::string base = "/tmp"; + + SECTION("an empty id returns base unchanged") { + REQUIRE(per_user_temp_dir(base, "") == base); + } + SECTION("a non-empty id is appended at the top level") { + REQUIRE(per_user_temp_dir(base, "1000") == base + "/orcaslicer_1000"); + } + SECTION("distinct ids produce distinct roots") { + REQUIRE(per_user_temp_dir(base, "1000") != per_user_temp_dir(base, "1001")); + } +} + +TEST_CASE("per_user_temp_id follows the platform contract", "[utils]") { + const std::string id = per_user_temp_id(); + + SECTION("stable across calls") { + REQUIRE(per_user_temp_id() == id); + } +#ifdef _WIN32 + SECTION("empty on Windows (its temp dir is already per-user)") { + REQUIRE(id.empty()); + } +#else + SECTION("the current uid on Linux/macOS") { + REQUIRE_FALSE(id.empty()); + REQUIRE(id == std::to_string(static_cast(::getuid()))); + } +#endif +} + +// The end-to-end contract callers depend on: the temp root is left alone on +// Windows and isolated per user on Linux/macOS. +TEST_CASE("per-user temp root is unchanged on Windows, isolated elsewhere", "[utils]") { + const std::string base = "/tmp"; + const std::string root = per_user_temp_dir(base, per_user_temp_id()); +#ifdef _WIN32 + REQUIRE(root == base); +#else + REQUIRE(root != base); + REQUIRE_THAT(root, Catch::Matchers::StartsWith(base + "/orcaslicer_")); +#endif +}