Keep the Sweep Out of the Write Helper and Honour a Read-Only Target

Sweeping the target's directory from inside write_file_atomically() made
every settings export into a user's folder delete their own numbered
files that matched the older config temporary form, and cost a directory
walk per save. The helper writes its target and nothing else; the user
preset scan, the bundle metadata reads and AppConfig::load() sweep the
directories the application owns, once, while they hold the lock.

Replacing a file needs only a writable directory, so a preset or config
the user made read-only was overwritten where the in-place write used to
fail; such a target is refused before anything is written. The CLI's
load_if_exists() takes no lock and creates no lock file, since the CLI
never saves. The lock guard holds the slot mutex through a unique_lock,
so an exception during construction cannot leave the slot locked for
good, and it counts its entry last so a throw leaves the slot as found;
the cool-down after a failed open is set where the failure is seen.

The cloud agent's sync state and secret fallback file and the 3DPrinterOS
session file wrote through a fixed ".tmp" name with a non-Unicode stream;
they call the helper. The vendor cache failure test makes the cache
read-only, which the helper refuses on every platform, and the utility
tests carry the PascalCase tag the test rules ask for.
This commit is contained in:
Hanif Koh
2026-09-24 19:40:26 +08:00
parent 79d7638852
commit a54b0493ce
11 changed files with 87 additions and 83 deletions
+5 -3
View File
@@ -2,6 +2,7 @@
#include <algorithm>
#include <sstream>
#include <system_error>
#include <exception>
#include <boost/format.hpp>
#include <boost/log/trivial.hpp>
@@ -580,9 +581,10 @@ bool C3DPrinterOS::save_api_session(const std::string &session, const std::strin
j.put("session", session);
j.put("email", email);
try {
auto temp_path = m_api_session_file_path + ".tmp";
pt::write_json(temp_path, j);
boost::filesystem::rename(temp_path, m_api_session_file_path);
std::ostringstream json;
pt::write_json(json, j);
if (const std::error_code ec = write_file_atomically(m_api_session_file_path, json.str()))
throw std::system_error(ec);
} catch (const std::exception &err) {
BOOST_LOG_TRIVIAL(error) << __FUNCTION__ << ": failed to write json to file. Path = "
<< m_api_session_file_path