From a804c1e35ff1493554f2df5fca7359e93221819b Mon Sep 17 00:00:00 2001 From: Ulya Trofimovich Date: Thu, 7 Mar 2019 13:08:51 +0000 Subject: [PATCH] Handle cases when rename() fails because destination file exists. In C/C++ rename() behaviour is implementation-defined. POSIX and Windows have different behaviour when destination file exists: POSIX says rename() should overwrite it, but Windows says rename() should fail. --- re2c/src/codegen/output.cc | 13 ++++++------ re2c/src/util/temp_file.cc | 43 ++++++++++++++++++++++++++++++++++---- re2c/src/util/temp_file.h | 1 + 3 files changed, 46 insertions(+), 11 deletions(-) diff --git a/re2c/src/codegen/output.cc b/re2c/src/codegen/output.cc index c1bb4c55..aa1684e5 100644 --- a/re2c/src/codegen/output.cc +++ b/re2c/src/codegen/output.cc @@ -438,16 +438,15 @@ bool Output::emit_blocks(const std::string &fname, blocks_t &blocks, const std::set &global_stags, const std::set &global_mtags) { - FILE *file = NULL; - bool temp = false; + FILE *file = NULL, *temp = NULL; std::string filename = fname, tempname = fname; if (filename.empty()) { filename = ""; file = stdout; } - else if ((file = temp_file(tempname))) { - temp = true; + else if ((temp = temp_file(tempname))) { + file = temp; } else if (!(file = fopen(filename.c_str(), "w"))) { error("cannot open output file %s", filename.c_str()); @@ -546,12 +545,12 @@ bool Output::emit_blocks(const std::string &fname, blocks_t &blocks, } fclose(file); - if (temp && rename(tempname.c_str(), fname.c_str()) != 0) { - error("cannot rename temporary file %s to output file %s" + if (temp && !overwrite_file(tempname.c_str(), fname.c_str())) { + error("cannot rename or write temporary file %s to output file %s" , tempname.c_str(), fname.c_str()); + remove(tempname.c_str()); return false; } - return true; } diff --git a/re2c/src/util/temp_file.cc b/re2c/src/util/temp_file.cc index 01457ad6..132d8148 100644 --- a/re2c/src/util/temp_file.cc +++ b/re2c/src/util/temp_file.cc @@ -1,6 +1,5 @@ #include "config.h" #include -#include "src/msg/msg.h" #include "src/util/temp_file.h" @@ -18,6 +17,7 @@ #define OPEN(fn) open(fn, O_CREAT | O_EXCL | O_RDWR, S_IRUSR | S_IWUSR) #define FDOPEN(fd) fdopen(fd, "w") #define CLOSE(fd) close(fd) +#define UNLINK(f) unlink(f) #elif defined(_MSC_VER) \ && defined(HAVE_IO_H) \ @@ -29,6 +29,7 @@ #define OPEN(fn) _open(fn, _O_CREAT | _O_EXCL | _O_RDWR, _S_IREAD | _S_IWRITE) #define FDOPEN(fd) _fdopen(fd, "w") #define CLOSE(fd) _close(fd) +#define UNLINK(f) _unlink(f) #else @@ -36,6 +37,7 @@ #define OPEN(fn) -1 #define FDOPEN(fd) NULL #define CLOSE -1 +#define UNLINK(f) -1 #endif @@ -61,14 +63,47 @@ FILE *temp_file(std::string &fname) } // we don't try too hard - if (!f) { - error("cannot open temporary file %s", fname.c_str()); - } return f; } +bool overwrite_file(const char *srcname, const char *dstname) +{ + // remove destination file no matter what + UNLINK(dstname); + + // try the easy way: rename + if (rename(srcname, dstname) == 0) return true; + + // rename failed: try write + FILE *src = NULL, *dst = NULL; + bool ok = false; + + src = fopen(srcname, "r"); + if (!src) goto end; + + dst = fopen(dstname, "w"); + if (!dst) goto end; + + static const size_t BLK = 4096; + char buf[BLK]; + for (;;) { + const size_t n = fread(buf, 1, BLK, src); + fwrite(buf, 1, n, dst); + if (n < BLK) break; + } + ok = true; + +end: + if (src) fclose(src); + if (dst) fclose(dst); + UNLINK(ok ? srcname : dstname); + + return ok; +} + } // namespace re2c #undef OPEN #undef FDOPEN #undef CLOSE +#undef UNLINK diff --git a/re2c/src/util/temp_file.h b/re2c/src/util/temp_file.h index 9b2e6d67..177b3d87 100644 --- a/re2c/src/util/temp_file.h +++ b/re2c/src/util/temp_file.h @@ -8,6 +8,7 @@ namespace re2c { FILE *temp_file(std::string &fname); +bool overwrite_file(const char *srcname, const char *dstname); } // namespace re2c -- 2.40.0