diff --git a/NEWS.md b/NEWS.md index b8aa5998..8ff49290 100644 --- a/NEWS.md +++ b/NEWS.md @@ -8,6 +8,10 @@ * `dir_exists()` follows relative symlinks in non-current directories (@heavywatal, #395). +* `file_copy(overwrite = TRUE)` no longer fails with `[EPERM]` when the + destination file is owned by a different user but the current user has write + permission via group membership (#487). + # fs 1.6.6 * No changes. diff --git a/src/file.cc b/src/file.cc index 0a697d86..a6b53f5d 100644 --- a/src/file.cc +++ b/src/file.cc @@ -368,6 +368,20 @@ fs_copyfile_(SEXP path_sxp, SEXP new_path_sxp, SEXP overwrite_sxp) { uv_fs_t req; const char* p = CHAR(STRING_ELT(path_sxp, i)); const char* n = CHAR(STRING_ELT(new_path_sxp, i)); + + // When overwriting, unlink the destination first so that fchmod() does not + // fail with EPERM when the destination is owned by a different user (but + // the current user has write permission via group membership). + if (overwrite) { + uv_fs_t unlink_req; + uv_fs_unlink(uv_default_loop(), &unlink_req, n, NULL); + // Ignore ENOENT (file didn't exist); propagate other errors. + if (unlink_req.result < 0 && unlink_req.result != UV_ENOENT) { + stop_for_error(unlink_req, "Failed to remove '%s'", n); + } + uv_fs_req_cleanup(&unlink_req); + } + uv_fs_copyfile( uv_default_loop(), &req, diff --git a/tests/testthat/test-copy.R b/tests/testthat/test-copy.R index b1184812..0565eafe 100644 --- a/tests/testthat/test-copy.R +++ b/tests/testthat/test-copy.R @@ -31,6 +31,12 @@ describe("file_copy", { expect_equal(readLines("bar2"), readLines("bar")) }) }) + it("can overwrite an existing file", { + with_dir_tree(list("foo" = "original", "bar" = "new content"), { + file_copy("bar", "foo", overwrite = TRUE) + expect_equal(readLines("foo"), "new content") + }) + }) it("errors on missing input", { expect_error(file_copy(NA, "foo2"), class = "invalid_argument") expect_error(file_copy("foo", NA), class = "invalid_argument")