Don't rely on utimensat()

utimensat() does not work on all filesystems, in particular on Windows.
This commit is contained in:
Matthias Schiffer 2017-06-13 16:39:17 +02:00
parent 6177612ea4
commit 292f4f4f3b
Signed by: neocturne
GPG key ID: 16EF3F64CB201D9C

View file

@ -30,6 +30,7 @@
#include "World/Region.hpp" #include "World/Region.hpp"
#include <cerrno> #include <cerrno>
#include <cinttypes>
#include <climits> #include <climits>
#include <cstdio> #include <cstdio>
#include <cstring> #include <cstring>
@ -44,7 +45,6 @@
#include <dirent.h> #include <dirent.h>
#include <fcntl.h> #include <fcntl.h>
#include <sys/stat.h> #include <sys/stat.h>
#include <sys/time.h>
namespace MinedMap { namespace MinedMap {
@ -52,27 +52,6 @@ namespace MinedMap {
static const size_t DIM = World::Region::SIZE*World::Chunk::SIZE; static const size_t DIM = World::Region::SIZE*World::Chunk::SIZE;
static inline bool operator<(const struct timespec &t1, const struct timespec &t2) {
return (t1.tv_sec < t2.tv_sec || (t1.tv_sec == t2.tv_sec && t1.tv_nsec < t2.tv_nsec));
}
static inline bool operator<=(const struct timespec &t1, const struct timespec &t2) {
return (t1.tv_sec < t2.tv_sec || (t1.tv_sec == t2.tv_sec && t1.tv_nsec <= t2.tv_nsec));
}
static inline bool operator>(const struct timespec &t1, const struct timespec &t2) {
return (t1.tv_sec > t2.tv_sec || (t1.tv_sec == t2.tv_sec && t1.tv_nsec > t2.tv_nsec));
}
static inline bool operator>=(const struct timespec &t1, const struct timespec &t2) {
return (t1.tv_sec > t2.tv_sec || (t1.tv_sec == t2.tv_sec && t1.tv_nsec >= t2.tv_nsec));
}
static inline bool operator==(const struct timespec &t1, const struct timespec &t2) {
return (t1.tv_sec == t2.tv_sec && t1.tv_nsec == t2.tv_nsec);
}
static void addChunk(uint32_t image[DIM*DIM], uint8_t lightmap[2*DIM*DIM], size_t X, size_t Z, const World::Chunk *chunk) { static void addChunk(uint32_t image[DIM*DIM], uint8_t lightmap[2*DIM*DIM], size_t X, size_t Z, const World::Chunk *chunk) {
World::Chunk::Blocks layer = chunk->getTopLayer(); World::Chunk::Blocks layer = chunk->getTopLayer();
@ -87,20 +66,38 @@ static void addChunk(uint32_t image[DIM*DIM], uint8_t lightmap[2*DIM*DIM], size_
} }
} }
static void writeImage(const std::string &output, const uint8_t *data, bool colored, const struct timespec *t) { static int64_t readStamp(const std::string &filename) {
int64_t v = INT64_MIN;
std::FILE *f = std::fopen((filename + ".stamp").c_str(), "r");
if (f) {
std::fscanf(f, "%" SCNd64, &v);
std::fclose(f);
}
return v;
}
static void writeStamp(const std::string &filename, int64_t v) {
std::FILE *f = std::fopen((filename + ".stamp").c_str(), "w");
if (f) {
std::fprintf(f, "%" PRId64, v);
std::fclose(f);
}
}
static void writeImage(const std::string &output, const uint8_t *data, bool colored, int64_t t) {
const std::string tmpfile = output + ".tmp"; const std::string tmpfile = output + ".tmp";
try { try {
PNG::write(tmpfile.c_str(), data, DIM, DIM, colored); PNG::write(tmpfile.c_str(), data, DIM, DIM, colored);
struct timespec times[2] = {*t, *t};
if (utimensat(AT_FDCWD, tmpfile.c_str(), times, 0) < 0)
std::fprintf(stderr, "Warning: failed to set utime on %s: %s\n", tmpfile.c_str(), std::strerror(errno));
if (std::rename(tmpfile.c_str(), output.c_str()) < 0) { if (std::rename(tmpfile.c_str(), output.c_str()) < 0) {
std::fprintf(stderr, "Unable to save %s: %s\n", output.c_str(), std::strerror(errno)); std::fprintf(stderr, "Unable to save %s: %s\n", output.c_str(), std::strerror(errno));
std::remove(tmpfile.c_str()); std::remove(tmpfile.c_str());
} }
writeStamp(output, t);
} }
catch (const std::exception& ex) { catch (const std::exception& ex) {
std::remove(tmpfile.c_str()); std::remove(tmpfile.c_str());
@ -109,17 +106,27 @@ static void writeImage(const std::string &output, const uint8_t *data, bool colo
} }
static void doRegion(const std::string &input, const std::string &output, const std::string &output_light) { static void doRegion(const std::string &input, const std::string &output, const std::string &output_light) {
struct stat instat, outstat; int64_t intime;
if (stat(input.c_str(), &instat) < 0) { {
std::fprintf(stderr, "Unable to stat %s: %s\n", input.c_str(), std::strerror(errno)); struct stat instat;
return;
if (stat(input.c_str(), &instat) < 0) {
std::fprintf(stderr, "Unable to stat %s: %s\n", input.c_str(), std::strerror(errno));
return;
}
intime = (int64_t)instat.st_mtim.tv_sec * 1000000 + instat.st_mtim.tv_nsec / 1000;
} }
if (stat(output.c_str(), &outstat) == 0) { {
if (instat.st_mtim <= outstat.st_mtim) { struct stat s;
std::printf("%s is up-to-date.\n", output.c_str()); if (stat(output.c_str(), &s) == 0) {
return; int64_t outtime = readStamp(output);
if (intime <= outtime) {
std::printf("%s is up-to-date.\n", output.c_str());
return;
}
} }
} }
@ -134,8 +141,8 @@ static void doRegion(const std::string &input, const std::string &output, const
World::Region::visitChunks(input.c_str(), [&] (size_t X, size_t Z, const World::Chunk *chunk) { addChunk(image.get(), lightmap.get(), X, Z, chunk); }); World::Region::visitChunks(input.c_str(), [&] (size_t X, size_t Z, const World::Chunk *chunk) { addChunk(image.get(), lightmap.get(), X, Z, chunk); });
writeImage(output, reinterpret_cast<const uint8_t*>(image.get()), true, &instat.st_mtim); writeImage(output, reinterpret_cast<const uint8_t*>(image.get()), true, intime);
writeImage(output_light, lightmap.get(), false, &instat.st_mtim); writeImage(output_light, lightmap.get(), false, intime);
} }
catch (const std::exception& ex) { catch (const std::exception& ex) {
std::fprintf(stderr, "Failed to generate %s: %s\n", output.c_str(), ex.what()); std::fprintf(stderr, "Failed to generate %s: %s\n", output.c_str(), ex.what());
@ -185,27 +192,33 @@ static bool makeMipmap(const std::string &dir, size_t level, size_t x, size_t z,
const char *sw = sw_str.c_str(); const char *sw = sw_str.c_str();
const char *se = se_str.c_str(); const char *se = se_str.c_str();
struct timespec t; int64_t t = INT64_MIN;
struct stat s;
size_t count = 0; size_t count = 0;
for (auto name : {&nw, &ne, &sw, &se}) { for (auto name : {&nw, &ne, &sw, &se}) {
struct stat s;
if (stat(*name, &s) < 0) { if (stat(*name, &s) < 0) {
*name = nullptr; *name = nullptr;
continue; continue;
} }
if (!count || s.st_mtim > t) int64_t t_part = readStamp(*name);
t = s.st_mtim; if (t_part > t)
t = t_part;
count++; count++;
} }
std::string output = outdir + formatTileName(x, z, "png"); std::string output = outdir + formatTileName(x, z, "png");
if (stat(output.c_str(), &s) == 0) {
ret = true;
if (!count || t <= s.st_mtim) {
return ret; struct stat s;
if (stat(output.c_str(), &s) == 0) {
ret = true;
int64_t outtime = readStamp(output);
if (t <= outtime)
return ret;
}
} }
if (!count) if (!count)
@ -216,14 +229,12 @@ static bool makeMipmap(const std::string &dir, size_t level, size_t x, size_t z,
try { try {
PNG::mipmap(tmpfile.c_str(), DIM, DIM, colored, nw, ne, sw, se); PNG::mipmap(tmpfile.c_str(), DIM, DIM, colored, nw, ne, sw, se);
struct timespec times[2] = {t, t};
if (utimensat(AT_FDCWD, tmpfile.c_str(), times, 0) < 0)
std::fprintf(stderr, "Warning: failed to set utime on %s: %s\n", tmpfile.c_str(), std::strerror(errno));
if (std::rename(tmpfile.c_str(), output.c_str()) < 0) { if (std::rename(tmpfile.c_str(), output.c_str()) < 0) {
std::fprintf(stderr, "Unable to save %s: %s\n", output.c_str(), std::strerror(errno)); std::fprintf(stderr, "Unable to save %s: %s\n", output.c_str(), std::strerror(errno));
std::remove(tmpfile.c_str()); std::remove(tmpfile.c_str());
} }
writeStamp(output, t);
} }
catch (const std::exception& ex) { catch (const std::exception& ex) {
std::remove(tmpfile.c_str()); std::remove(tmpfile.c_str());