Commit 57821d89 authored by Ramon Nou's avatar Ramon Nou
Browse files

fix(malleability): use atomic rename to prevent hostfile race condition

write_rs_interval_comments now writes to a temporary file and renames it
to the target path instead of truncating the file in place. This prevents
a race condition where concurrently running daemons could observe an empty
or partially written hostfile. Added cleanup for the temporary file on
failure and updated error messages accordingly.
parent 1527977f
Loading
Loading
Loading
Loading
Loading
+27 −3
Original line number Diff line number Diff line
@@ -33,9 +33,12 @@
#include <stdexcept>
#include <algorithm>
#include <cstring>
#include <cstdio>
#include <iomanip>
#include <fmt/format.h>

#include <unistd.h>

namespace gkfs {
namespace malleable {

@@ -258,11 +261,17 @@ write_rs_interval_comments(
    }
    in.close();

    std::ofstream out(path, std::ios::trunc);
    // Write to a sibling temporary file and rename it into place.  Mutate
    // start sends this shared hostfile to every daemon concurrently; opening
    // the destination with std::ios::trunc leaves a window in which a peer can
    // observe an empty or partially-written hostfile.
    const auto temporary_path =
            path + ".tmp." + std::to_string(static_cast<long long>(getpid()));
    std::ofstream out(temporary_path, std::ios::trunc);
    if(!out.is_open()) {
        throw std::runtime_error(fmt::format(
                "Failed to write hostfile for RS intervals: '{}': {}", path,
                strerror(errno)));
                "Failed to write temporary hostfile for RS intervals: '{}': {}",
                temporary_path, strerror(errno)));
    }
    for(const auto& kept_line : lines) {
        out << kept_line << "\n";
@@ -272,6 +281,21 @@ write_rs_interval_comments(
            << " start=" << std::fixed << std::setprecision(9) << interval.start
            << " end=" << interval.end << "\n";
    }
    out.close();
    if(!out) {
        std::remove(temporary_path.c_str());
        throw std::runtime_error(fmt::format(
                "Failed to write temporary hostfile for RS intervals: '{}': {}",
                temporary_path, strerror(errno)));
    }

    if(std::rename(temporary_path.c_str(), path.c_str()) != 0) {
        const auto error = errno;
        std::remove(temporary_path.c_str());
        throw std::runtime_error(fmt::format(
                "Failed to replace hostfile for RS intervals: '{}': {}", path,
                strerror(error)));
    }
}

} // namespace malleable