]> granicus.if.org Git - re2c/commitdiff
Handle cases when rename() fails because destination file exists.
authorUlya Trofimovich <skvadrik@gmail.com>
Thu, 7 Mar 2019 13:08:51 +0000 (13:08 +0000)
committerUlya Trofimovich <skvadrik@gmail.com>
Thu, 7 Mar 2019 13:08:51 +0000 (13:08 +0000)
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
re2c/src/util/temp_file.cc
re2c/src/util/temp_file.h

index c1bb4c552365d8bc986daeb803062c9c68ea9020..aa1684e5e240073a9a84b715a6328e8b0c9051af 100644 (file)
@@ -438,16 +438,15 @@ bool Output::emit_blocks(const std::string &fname, blocks_t &blocks,
     const std::set<std::string> &global_stags,
     const std::set<std::string> &global_mtags)
 {
-    FILE *file = NULL;
-    bool temp = false;
+    FILE *file = NULL, *temp = NULL;
     std::string filename = fname, tempname = fname;
 
     if (filename.empty()) {
         filename = "<stdout>";
         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;
 }
 
index 01457ad6aee734168bc6a45e540b1074706a043a..132d814893e2b5fd0a5ac82a36947fd31bf1c158 100644 (file)
@@ -1,6 +1,5 @@
 #include "config.h"
 #include <time.h>
-#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
index 9b2e6d679838060f248d671a094f2d8aff7e1b3e..177b3d873c4b7e46abbddf3b8f17f3b5cb2fc2ac 100644 (file)
@@ -8,6 +8,7 @@
 namespace re2c {
 
 FILE *temp_file(std::string &fname);
+bool overwrite_file(const char *srcname, const char *dstname);
 
 } // namespace re2c