mirror of
https://github.com/OrcaSlicer/OrcaSlicer.git
synced 2026-09-27 02:41:17 +00:00
Never Mistake a Numbered Backup for a Temporary and Check Real Write Access
The sweep's prefix form counted any <name>.<digits> file as a leftover of the old config writer, so a user's OrcaSlicer.conf.1 backup went at the next start. Only the <name>.<pid>.<n>.tmp form is a temporary now, and the name is tested before the entry is stat'ed. The read-only check went by mode bits, which misses a deny-write ACL on Windows and refuses a root-owned file root can write; it asks the OS with access() instead. The two-step rename fallback runs only for the errors a refused replace produces, never for an I/O error that would only lose the target, and says so if the second step fails after the old file is gone. The orphaned-.info scan on the sync thread reads each .info under the lock, so a remove_files() in another instance is seen whole or not at all, and queues the cloud delete after the lock is released, keeping the queue mutex outside it. AppConfig::save() assembles its text before it takes the lock, like Preset::save(). The two bundle loops share one metadata loader, and the two-thread write test counts leftover temporaries rather than every directory entry, since a scanner on Windows may hold the old file under another name for a moment.
This commit is contained in:
@@ -155,12 +155,13 @@ TEST_CASE("write_file_atomically survives two threads writing one target", "[Uti
|
||||
load_string_file(target, content);
|
||||
const bool whole = content == a || content == b;
|
||||
REQUIRE(whole);
|
||||
size_t entries = 0;
|
||||
for (auto &entry : boost::filesystem::directory_iterator(dir.path())) {
|
||||
(void) entry;
|
||||
++entries;
|
||||
}
|
||||
REQUIRE(entries == 1);
|
||||
// No temporary may be left; a scanner on Windows may briefly hold the old
|
||||
// file under another name, so only the temporaries are counted.
|
||||
size_t temporaries = 0;
|
||||
for (auto &entry : boost::filesystem::directory_iterator(dir.path()))
|
||||
if (entry.path().extension() == ".tmp")
|
||||
++temporaries;
|
||||
REQUIRE(temporaries == 0);
|
||||
}
|
||||
|
||||
TEST_CASE("remove_stale_temp_files removes only old <name>.<pid>.tmp files", "[Utils]") {
|
||||
@@ -173,11 +174,11 @@ TEST_CASE("remove_stale_temp_files removes only old <name>.<pid>.tmp files", "[U
|
||||
// Just written: possibly another instance's in-flight save, so it stays.
|
||||
REQUIRE_FALSE(write_file_atomically((dir.path() / "g.json.7.tmp").string(), "x"));
|
||||
|
||||
SECTION("with a name prefix only matching names go, including the older <name>.<pid> form") {
|
||||
REQUIRE(remove_stale_temp_files(dir.path(), "a.json") == 3);
|
||||
SECTION("with a name prefix only matching names go, and a numbered backup is not a temporary") {
|
||||
REQUIRE(remove_stale_temp_files(dir.path(), "a.json") == 2);
|
||||
REQUIRE_FALSE(boost::filesystem::exists(dir.path() / "a.json.123.tmp"));
|
||||
REQUIRE_FALSE(boost::filesystem::exists(dir.path() / "a.json.99"));
|
||||
REQUIRE_FALSE(boost::filesystem::exists(dir.path() / "a.json.12.3.tmp"));
|
||||
REQUIRE(boost::filesystem::exists(dir.path() / "a.json.99"));
|
||||
REQUIRE(boost::filesystem::exists(dir.path() / "b.info.4.tmp"));
|
||||
}
|
||||
SECTION("without a prefix every stale temporary goes and nothing else") {
|
||||
|
||||
Reference in New Issue
Block a user