libuv icon indicating copy to clipboard operation
libuv copied to clipboard

fs: bring back macos-specific copyfile(3)

Open Artoria2e5 opened this issue 5 years ago • 62 comments

copyfile(3) does not always do its own open according to permission rules. We can solve that by guarding it with one of our own opens. This way we can keep the cool clonefile thing for huge node_modules and still have sensible results.

MacOS does not have faccess(2) in case you are wondering why I did this. There is only access(2) with the "real UID" shenanigans that will bite us in the future.

This PR partially reverts #2233. Tests in the said PR are kept, and they should pass.

Artoria2e5 avatar Dec 16 '19 12:12 Artoria2e5

We need to do an open first per nodejs/node#26936. Amending.

For the record, this one is possibly due to this part in copyfile: if(chmod(s->dst, (s->sb.st_mode | S_IWUSR) & ~S_IFMT) == 0). Absolute unit.

For clonefileat it should fail correctly, since COPYFILE_UNLINK is not default and the syscall always fails when dest exists

Artoria2e5 avatar Dec 16 '19 12:12 Artoria2e5

fstat is not the best idea because ACL is a thing on macOS. The correct way to do so is via the access(2) family of APIs, but macOS does not have a faccess(2) / faccessat(2) that does EUID instead of the UID with AT_EACCESS. A quick Google search I did yesterday finds CarbonStdCLib.o, apparently a C library polyfill, doing something similar for faccess.

Unlinking is what the OS X cp(1) command does for -c anyways, so it kind of is idiomatic in this use.

Gonna fix the branch thing later today (UTC+8).

Artoria2e5 avatar Dec 17 '19 02:12 Artoria2e5

Hmm, looks like there is faccessat(2) on macOS >= 10.10. What's the OS support policy for libuv and nodejs? (Looks like I can do that for node, which only supports macOS >= 10.11.)

Artoria2e5 avatar Dec 17 '19 02:12 Artoria2e5

libuv supports macOS >= 10.7. See the list of supported platforms here.

cjihrig avatar Dec 17 '19 14:12 cjihrig

A good-ish polyfill for our desired functionality is found in gnulib euidaccess(3) (GPL!!). It gives two implementations: the correct but non-reentrant one using a setre{u,g}id-access-setre{u,g}id pattern and the traditional stat thing which is incorrect with ACL. So which one is more evil here, changing a global state or being incorrect in a case not yet tested?

Since faccessat(2) is a syscall, I can possibly get away with doing a version check like the clone part and syscall(2) the whole thing away on 10.10+. I still need to pick a way to polyfill this feature for [10.7, 10.10), so please do give your suggestion on that. It should look like:

#if defined(__APPLE__) && !TARGET_OS_IPHONE
/** @returns 1: writeable. 2: does not exist. */
static int fs__mac_canwrite(const char* path, int darwin_kern_maj) {
  int res = 0;
  // 10.7+
  if (darwin_kern_maj >= 11) { 
    // TODO: rewrite to use syscall(2) and sys/syscall.h!
    res = faccessat(AT_FDCWD, path, W_OK | R_OK, AT_EACCESS) == 0;
    goto test_exist;
  }
  /* polyfill here */
test_exist: // should be reused by stat or access polyfills
  if (res == 0 && errorno == ENOENT)
    return 2;
  return res;
}

static bool fs__mac_can_copy(const char* path, int darwin_kern_maj, int excl) {
  int res;
  res = fs__mac_canwrite(path, darwin_kern_maj);
  if (res != 2)
    return ! excl;
  // Apple manpage says their dirname does not modify the input. Trust it for now.
  return !! fs__mac_canwrite(dirname(path), darwin_kern_maj);
}
#endif

Artoria2e5 avatar Dec 17 '19 15:12 Artoria2e5

I presume the gnulib implementation will be GPL and therefore cannot be used in libuv.

richardlau avatar Dec 17 '19 16:12 richardlau

Yeah, someone will need to wipe their brain with only the phrase "implement an access check based on stat() or setuid and access(2)" left if they decided to implement it here. Should possibly add a click warning on the gnulib link.

Artoria2e5 avatar Dec 17 '19 16:12 Artoria2e5

This issue has been automatically marked as stale because it has not had recent activity. It will be closed if no further activity occurs. Thank you for your contributions.

stale[bot] avatar Jan 13 '20 18:01 stale[bot]

This issue has been automatically marked as stale because it has not had recent activity. It will be closed if no further activity occurs. Thank you for your contributions.

stale[bot] avatar Jul 07 '20 15:07 stale[bot]

With this change, the macOS implementation has more overlap with the Unix implementation than it previously did. The overlap is more prone to getting out of sync - for example, the Unix implementation no longer opens the destination with O_TRUNC like this PR does.

Maybe we should look into using fcopyfile() only for clone operations and use the common implementation for everything else. It would essentially be a macOS specific version of this code block.

cjihrig avatar Aug 09 '20 21:08 cjihrig

Makes sense. An open() is needed for the perm check anyways.

The chmod() stuff is a bit extra, but ehh... let it slide.

Artoria2e5 avatar Aug 10 '20 14:08 Artoria2e5

Meh, reasoning through the open fds and fallbacks with clonefile(2) is still too hairy for me.

  • I need to unlink the file for clonefile to work, or it would fail with EEXIST. Yeah it takes paths, not fds.
  • If clonefile does fail for some other reason, I would need to do the very verbose open thing again. That's even uglier than two separate blocks.

Artoria2e5 avatar Aug 11 '20 14:08 Artoria2e5

@Artoria2e5 Is this ready for re-review or are you still working things out?

bnoordhuis avatar Sep 01 '20 19:09 bnoordhuis

There isn't much I can change right now, so please just throw more comments at me.

Artoria2e5 avatar Sep 01 '20 23:09 Artoria2e5

Meh, reasoning through the open fds and fallbacks with clonefile(2) is still too hairy for me.

FWIW, I was suggesting to use fcopyfile() since it works with fds and doesn't require unlinking the file first.

cjihrig avatar Sep 02 '20 00:09 cjihrig

That wouldn't be possible since fcopyfile() does not perform cloning. I am not sure if it's in the documentation, since copyfile.c is a bit easier to Google.

(If this was any other vendor, I would've figured that it's impossible since the lower level syscall doesn't work with fds. But everything's possible with Apple :)

Artoria2e5 avatar Sep 02 '20 13:09 Artoria2e5

This issue has been automatically marked as stale because it has not had recent activity. It will be closed if no further activity occurs. Thank you for your contributions.

stale[bot] avatar Oct 13 '20 05:10 stale[bot]

Oi…

Artoria2e5 avatar Oct 13 '20 13:10 Artoria2e5

This issue has been automatically marked as stale because it has not had recent activity. It will be closed if no further activity occurs. Thank you for your contributions.

stale[bot] avatar Nov 05 '20 01:11 stale[bot]

I don't think there is anything more I can do.

Artoria2e5 avatar Nov 05 '20 01:11 Artoria2e5

File cloning is very important to package managers. This makes this PR very important for a lot of people, especially when a good chunk of the JS developer ecosystem uses OSX on their day-to-day work 😕

arcanis avatar Nov 05 '20 09:11 arcanis

This issue has been automatically marked as stale because it has not had recent activity. It will be closed if no further activity occurs. Thank you for your contributions.

stale[bot] avatar Nov 26 '20 21:11 stale[bot]

Bump. I refuse to let this PR die.

wojtekmaj avatar Dec 10 '20 23:12 wojtekmaj

That wouldn't be possible since fcopyfile() does not perform cloning.

FWIW, I tested fcopyfile() with the COPYFILE_CLONE_FORCE, and it appeared to work (at least it didn't return an error):

diff --git a/src/unix/fs.c b/src/unix/fs.c
index 88ac0731..6186289a 100644
--- a/src/unix/fs.c
+++ b/src/unix/fs.c
@@ -62,6 +62,7 @@
 #endif
 
 #if defined(__APPLE__)
+# include <copyfile.h>
 # include <sys/sysctl.h>
 #elif defined(__linux__) && !defined(FICLONE)
 # include <sys/ioctl.h>
@@ -1259,6 +1260,37 @@ static ssize_t uv__fs_copyfile(uv_fs_t* req) {
       goto out;
     }
   }
+#elif defined(__APPLE__) && !TARGET_OS_IPHONE
+  if (req->flags & UV_FS_COPYFILE_FICLONE ||
+      req->flags & UV_FS_COPYFILE_FICLONE_FORCE) {
+    static int can_clone;
+    copyfile_flags_t flags;
+    char buf[64];
+    size_t len;
+    int major;
+
+    /* Check OS version. Cloning is only supported on macOS >= 10.12. */
+    if (req->flags & UV_FS_COPYFILE_FICLONE_FORCE) {
+      if (can_clone == 0) {
+        len = sizeof(buf);
+        if (sysctlbyname("kern.osrelease", buf, &len, NULL, 0))
+          return UV__ERR(errno);
+
+        if (1 != sscanf(buf, "%d", &major))
+          abort();
+
+        can_clone = -1 + 2 * (major >= 16);  /* macOS >= 10.12 */
+      }
+
+      if (can_clone < 0)
+        return UV_ENOSYS;
+    }
+
+    flags = COPYFILE_ALL | (1 << 25 /* COPYFILE_CLONE_FORCE */);
+    err = fcopyfile(srcfd, dstfd, NULL, flags);
+    if (err == 0 || req->flags & UV_FS_COPYFILE_FICLONE_FORCE)
+      goto out;
+  }
 #else
   if (req->flags & UV_FS_COPYFILE_FICLONE_FORCE) {
     err = UV_ENOSYS;

cjihrig avatar Jan 05 '21 22:01 cjihrig

I may have spoken too soon:

     COPYFILE_CLONE_FORCE   Clone the file instead.  This is a force flag i.e.
                            if cloning fails, an error is returned.  This flag
                            is equivalent to (COPYFILE_EXCL | COPYFILE_ACL |
                            COPYFILE_STAT | COPYFILE_XATTR | COPYFILE_DATA |
                            COPYFILE_NOFOLLOW_SRC).  Note that if cloning is
                            successful, progress callbacks will not be
                            invoked.  Note also that there is no support for
                            cloning directories: if a directory is provided as
                            the source, an error will be returned.  (This is
                            only applicable for the copyfile() function.)

cjihrig avatar Jan 05 '21 22:01 cjihrig

This issue has been automatically marked as stale because it has not had recent activity. It will be closed if no further activity occurs. Thank you for your contributions.

stale[bot] avatar Jan 30 '21 10:01 stale[bot]

Ping to prevent staleclose?

arcanis avatar Feb 17 '21 18:02 arcanis

This issue has been automatically marked as stale because it has not had recent activity. It will be closed if no further activity occurs. Thank you for your contributions.

stale[bot] avatar Mar 20 '21 00:03 stale[bot]

ping

zkochan avatar Mar 20 '21 00:03 zkochan

This issue has been automatically marked as stale because it has not had recent activity. It will be closed if no further activity occurs. Thank you for your contributions.

stale[bot] avatar Jun 02 '21 16:06 stale[bot]

ping

noahnu avatar Jun 02 '21 18:06 noahnu

This issue has been automatically marked as stale because it has not had recent activity. It will be closed if no further activity occurs. Thank you for your contributions.

stale[bot] avatar Jun 26 '21 01:06 stale[bot]

Ping 🏓

wojtekmaj avatar Jun 26 '21 03:06 wojtekmaj

This issue has been automatically marked as stale because it has not had recent activity. It will be closed if no further activity occurs. Thank you for your contributions.

stale[bot] avatar Jul 23 '21 05:07 stale[bot]

Bad bot

wojtekmaj avatar Jul 23 '21 05:07 wojtekmaj

This issue has been automatically marked as stale because it has not had recent activity. It will be closed if no further activity occurs. Thank you for your contributions.

stale[bot] avatar Aug 13 '21 23:08 stale[bot]

ping

mischnic avatar Aug 14 '21 08:08 mischnic

Do I summarize correctly this pull request is blocked indefinitely as long as libuv supports old macos versions?

Personally, I'm okay with raising the minimum version (10.7 is 10 years old) but, on the other hand, I know there are still a handful of users left.

bnoordhuis avatar Aug 14 '21 20:08 bnoordhuis

Well it's not like they couldn't just... Use the old version. It's what macOS 10.7 users are very much into anyway.

wojtekmaj avatar Aug 14 '21 21:08 wojtekmaj

This issue has been automatically marked as stale because it has not had recent activity. It will be closed if no further activity occurs. Thank you for your contributions.

stale[bot] avatar Sep 04 '21 23:09 stale[bot]

What value does this stale bot bring to the conversation?

zkochan avatar Sep 04 '21 23:09 zkochan

This issue has been automatically marked as stale because it has not had recent activity. It will be closed if no further activity occurs. Thank you for your contributions.

stale[bot] avatar Oct 02 '21 01:10 stale[bot]

It's really quite frustrating indeed. @bnoordhuis @cjihrig what does the OP need to do to bring this PR to the finish line?

I think this is in dire need of a concrete action item.

arcanis avatar Oct 02 '21 06:10 arcanis

Adding a + 1 too, just ran into this issue on Mac using pnpm + git worktrees.

uvesten avatar Oct 21 '21 21:10 uvesten

This issue has been automatically marked as stale because it has not had recent activity. Thank you for your contributions.

stale[bot] avatar Nov 25 '21 21:11 stale[bot]

@bnoordhuis @cjihrig Anything would be a blocker here ?

gengjiawen avatar Nov 26 '21 02:11 gengjiawen

This issue has been automatically marked as stale because it has not had recent activity. Thank you for your contributions.

stale[bot] avatar Jan 09 '22 01:01 stale[bot]

ping

zkochan avatar Jan 09 '22 01:01 zkochan

To give you a sense of the impact here — bun’s implementation of fs.copyFileSync which uses copyfile() on macOS performs 2x faster than node’s fs.copyFileSync when copying a 1 GB file.

59392F5C-1225-4D3E-9A9E-7D8E1FFDF571

Jarred-Sumner avatar Jan 13 '22 00:01 Jarred-Sumner

I opened #3406 earlier this week to raise the baseline to macOS 10.15. When accepted and merged, I think that would unblock this PR.

bnoordhuis avatar Jan 13 '22 05:01 bnoordhuis

To give you a sense of the impact here — bun’s implementation of fs.copyFileSync which uses copyfile() on macOS performs 2x faster than node’s fs.copyFileSync when copying a 1 GB file.

That's good, but I thought the main issue here was that APFS Copy on Write is not supported. When it is supported, the speedup should be closer to infinity than 2x.

uvesten avatar Jan 14 '22 13:01 uvesten

This wasn’t with the COPYFILE_FICLONE flag, so whether or not it used copy on write is up to the implementation of copyfile()

On Fri, Jan 14, 2022 at 5:09 AM Petter Uvesten @.***> wrote:

To give you a sense of the impact here — bun’s implementation of fs.copyFileSync which uses copyfile() on macOS performs 2x faster than node’s fs.copyFileSync when copying a 1 GB file.

That's good, but I thought the main issue here was that APFS Copy on Write is not supported. When it is supported, the speedup should be closer to infinity than 2x.

— Reply to this email directly, view it on GitHub https://github.com/libuv/libuv/pull/2578#issuecomment-1013102928, or unsubscribe https://github.com/notifications/unsubscribe-auth/AAFNGS7IQTIBOXES67KGINLUWAN7NANCNFSM4J3JET2A . You are receiving this because you commented.Message ID: @.***>

Jarred-Sumner avatar Jan 14 '22 13:01 Jarred-Sumner

The baseline is now macos 10.15. There's one remaining comment from @cjihrig so I defer to him on whether it's ready for merging. Let me know if I should also review.

bnoordhuis avatar Feb 08 '22 13:02 bnoordhuis

This issue has been automatically marked as stale because it has not had recent activity. Thank you for your contributions.

stale[bot] avatar Apr 16 '22 03:04 stale[bot]

Never Gonna Give You Up

zkochan avatar Apr 16 '22 12:04 zkochan

Adding my comment so my anti stale bot will take care of this pr from now on

FezVrasta avatar Apr 16 '22 16:04 FezVrasta

There's some unaddressed feedback I left in February. Most is stylistic but the faccessat() question needs an answer before this can go in.

Also, a gentle reminder that bots and their owners are likely to meet with swift and permanent bans.

bnoordhuis avatar Apr 16 '22 16:04 bnoordhuis

This issue has been automatically marked as stale because it has not had recent activity. Thank you for your contributions.

stale[bot] avatar Jun 12 '22 17:06 stale[bot]

Where is the anti stale bot?

zkochan avatar Jun 12 '22 18:06 zkochan

I've added the not-stale label. Hopefully that will keep stale bot away.

cjihrig avatar Jun 12 '22 18:06 cjihrig

@Artoria2e5 Do you plan on picking up this pull request again? If not, maybe someone else wants to step up and get this over the finish line?

bnoordhuis avatar Jun 12 '22 20:06 bnoordhuis

If not, maybe someone else wants to step up and get this over the finish line?

I've tried to incorporate your feedback: https://github.com/libuv/libuv/pull/3654

mischnic avatar Jun 22 '22 10:06 mischnic

In light of an updated PR being available I am closing this one. Just don't have time for code when I am all stuck at McD, I am afraid.

Artoria2e5 avatar Sep 07 '22 07:09 Artoria2e5