diff --git a/src/unix/fs.c b/src/unix/fs.c index eb17fb4a..362c36c6 100644 --- a/src/unix/fs.c +++ b/src/unix/fs.c @@ -946,6 +946,71 @@ static int uv__is_buggy_cephfs(int fd) { return uv__kernel_version() < /* 4.20.0 */ 0x041400; } + + +static int uv__is_cifs_or_smb(int fd) { + struct statfs s; + + if (-1 == fstatfs(fd, &s)) + return 0; + + switch ((unsigned) s.f_type) { + case 0x0000517Bu: /* SMB */ + case 0xFE534D42u: /* SMB2 */ + case 0xFF534D42u: /* CIFS */ + return 1; + } + + return 0; +} + + +static ssize_t uv__fs_try_copy_file_range(int in_fd, off_t* off, + int out_fd, size_t len) { + static int no_copy_file_range_support; + ssize_t r; + + if (uv__load_relaxed(&no_copy_file_range_support)) { + errno = ENOSYS; + return -1; + } + + r = uv__fs_copy_file_range(in_fd, off, out_fd, NULL, len, 0); + + if (r != -1) + return r; + + switch (errno) { + case EACCES: + /* Pre-4.20 kernels have a bug where CephFS uses the RADOS + * copy-from command when it shouldn't. + */ + if (uv__is_buggy_cephfs(in_fd)) + errno = ENOSYS; /* Use fallback. */ + break; + case ENOSYS: + uv__store_relaxed(&no_copy_file_range_support, 1); + break; + case EPERM: + /* It's been reported that CIFS spuriously fails. + * Consider it a transient error. + */ + if (uv__is_cifs_or_smb(out_fd)) + errno = ENOSYS; /* Use fallback. */ + break; + case ENOTSUP: + case EXDEV: + /* ENOTSUP - it could work on another file system type. + * EXDEV - it will not work when in_fd and out_fd are not on the same + * mounted filesystem (pre Linux 5.3) + */ + errno = ENOSYS; /* Use fallback. */ + break; + } + + return -1; +} + #endif /* __linux__ */ @@ -960,40 +1025,21 @@ static ssize_t uv__fs_sendfile(uv_fs_t* req) { { off_t off; ssize_t r; + size_t len; + int try_sendfile; off = req->off; + len = req->bufsml[0].len; + try_sendfile = 1; #ifdef __linux__ - { - static int no_copy_file_range_support; - - if (uv__load_relaxed(&no_copy_file_range_support) == 0) { - r = uv__fs_copy_file_range(in_fd, &off, out_fd, NULL, req->bufsml[0].len, 0); - - if (r == -1 && errno == ENOSYS) { - /* ENOSYS - it will never work */ - errno = 0; - uv__store_relaxed(&no_copy_file_range_support, 1); - } else if (r == -1 && errno == EACCES && uv__is_buggy_cephfs(in_fd)) { - /* EACCES - pre-4.20 kernels have a bug where CephFS uses the RADOS - copy-from command when it shouldn't */ - errno = 0; - uv__store_relaxed(&no_copy_file_range_support, 1); - } else if (r == -1 && (errno == ENOTSUP || errno == EXDEV)) { - /* ENOTSUP - it could work on another file system type */ - /* EXDEV - it will not work when in_fd and out_fd are not on the same - mounted filesystem (pre Linux 5.3) */ - errno = 0; - } else { - goto ok; - } - } - } + r = uv__fs_try_copy_file_range(in_fd, &off, out_fd, len); + try_sendfile = (r == -1 && errno == ENOSYS); #endif - r = sendfile(out_fd, in_fd, &off, req->bufsml[0].len); + if (try_sendfile) + r = sendfile(out_fd, in_fd, &off, len); -ok: /* sendfile() on SunOS returns EINVAL if the target fd is not a socket but * it still writes out data. Fortunately, we can detect it by checking if * the offset has been updated. @@ -1277,22 +1323,15 @@ static ssize_t uv__fs_copyfile(uv_fs_t* req) { if (fchmod(dstfd, src_statsbuf.st_mode) == -1) { err = UV__ERR(errno); #ifdef __linux__ + /* fchmod() on CIFS shares always fails with EPERM unless the share is + * mounted with "noperm". As fchmod() is a meaningless operation on such + * shares anyway, detect that condition and squelch the error. + */ if (err != UV_EPERM) goto out; - { - struct statfs s; - - /* fchmod() on CIFS shares always fails with EPERM unless the share is - * mounted with "noperm". As fchmod() is a meaningless operation on such - * shares anyway, detect that condition and squelch the error. - */ - if (fstatfs(dstfd, &s) == -1) - goto out; - - if ((unsigned) s.f_type != /* CIFS */ 0xFF534D42u) - goto out; - } + if (!uv__is_cifs_or_smb(dstfd)) + goto out; err = 0; #else /* !__linux__ */