The bug here was that moar expected libuv sendfile to send all bytes that are requested in one go, but it doesn't do that on linux (and perhaps other platforms). Instead, 2G are sent at once. libuv sendfile differs a bit from standard POSIX sendfile, but I think this patch fixes it.
Bart 2015-02-11 18:41 GMT+01:00 Elizabeth Mattijsen <l...@dijkmat.nl>: > > On 11 Feb 2015, at 15:51, Solomon Foster (via RT) < > perl6-bugs-follo...@perl.org> wrote: > > > > # New Ticket Created by Solomon Foster > > # Please include the string: [perl #123796] > > # in the subject line of all future correspondence about this issue. > > # <URL: https://rt.perl.org/Ticket/Display.html?id=123796 > > > > > > > This is perl6 version 2015.01-126-gf53a94d built on MoarVM version > > 2015.01-28-ga910556 > > > >> copy("original.mpg".IO, "copy.mpg".IO) > > True > >> > > colomon@melissa:~/temp$ ls -l *.mpg > > -rw-rw-r-- 1 colomon colomon 2147479552 Feb 11 09:51 copy.mpg > > -rw-r--r-- 1 colomon colomon 4773115509 Feb 11 09:50 original.mpg > > Internally this is handled by the nqp::copy op. I would bet that only the > first 2G is copied because that’s what you can have in a signed 4byte int. > > > Liz
diff --git a/src/io/fileops.c b/src/io/fileops.c index 2bdded8..3e446a0 100644 --- a/src/io/fileops.c +++ b/src/io/fileops.c @@ -118,41 +118,83 @@ MVMint64 MVM_file_stat(MVMThreadContext *tc, MVMString *filename, MVMint64 statu return r; } -/* copy a file from one to another. */ -void MVM_file_copy(MVMThreadContext *tc, MVMString *src, MVMString *dest) { +/* copy a file from one to another */ +void MVM_file_copy(MVMThreadContext *tc, MVMString *src, MVMString * dest) { + /* TODO: on Windows we can use the CopyFile API, which is probaly + more efficient, not to mention easier to use. */ uv_fs_t req; - char * const a = MVM_string_utf8_encode_C_string(tc, src); - const uv_file in_fd = uv_fs_open(tc->loop, &req, (const char *)a, O_RDONLY, 0, NULL); + char * a, * b; + uv_file in_fd = -1, out_fd = -1; + MVMuint64 size, offset; - if (in_fd >= 0 && uv_fs_stat(tc->loop, &req, a, NULL) >= 0) { - char * const b = MVM_string_utf8_encode_C_string(tc, dest); - const uv_file out_fd = uv_fs_open(tc->loop, &req, (const char *)b, O_WRONLY | O_CREAT | O_TRUNC, DEFAULT_MODE, NULL); - MVM_free(a); + a = MVM_string_utf8_encode_C_string(tc, src); + b = MVM_string_utf8_encode_C_string(tc, dest); - if (out_fd >= 0 - && uv_fs_sendfile(tc->loop, &req, out_fd, in_fd, 0, req.statbuf.st_size, NULL) >= 0) { - MVM_free(b); + /* If the file cannot be stat(), there is little point in going any further. */ + if (uv_fs_stat(tc->loop, &req, a, NULL) < 0) + goto failure; + size = req.statbuf.st_size; - if (uv_fs_close(tc->loop, &req, in_fd, NULL) < 0) { - uv_fs_close(tc->loop, &req, out_fd, NULL); /* should close out_fd before throw. */ - MVM_exception_throw_adhoc(tc, "Failed to close file: %s", uv_strerror(req.result)); - } + in_fd = uv_fs_open(tc->loop, &req, (const char *)a, O_RDONLY, 0, NULL); + if (in_fd < 0) { + goto failure; + } - if (uv_fs_close(tc->loop, &req, out_fd, NULL) < 0) { - MVM_exception_throw_adhoc(tc, "Failed to close file: %s", uv_strerror(req.result)); - } + out_fd = uv_fs_open(tc->loop, &req, (const char *)b, O_WRONLY | O_CREAT | O_TRUNC, DEFAULT_MODE, NULL); + if (out_fd < 0) { + goto failure; + } - return; + offset = 0; + do { + /* sendfile() traditionally takes offset as a pointer argument + * used a both input and output. libuv deviates by making + * offset an integer and returning the number of bytes + * sent. So it is necessary to add these explicitly. */ + MVMint64 sent = uv_fs_sendfile(tc->loop, &req, out_fd, in_fd, offset, size - offset, NULL); + if (sent < 0) { + goto failure; } - else - MVM_free(b); + offset += sent; + } while (offset < size); + + /* Cleanup */ + if(uv_fs_close(tc->loop, &req, in_fd, NULL) < 0) { + goto failure; } - else - MVM_free(a); + in_fd = -1; - MVM_exception_throw_adhoc(tc, "Failed to copy file: %s", uv_strerror(req.result)); + if (uv_fs_close(tc->loop, &req, out_fd, NULL) < 0) { + goto failure; + } + out_fd = -1; + + MVM_free(b); + MVM_free(a); + return; + + failure: { + /* First get the error, since it may be overwritten further on. */ + const char * error = uv_strerror(req.result); + /* Basic premise: dealing with all failure cases is hard. + * So to simplify, a and b are allocated in all conditions. + * Also to simplify, in_fd are nonnegative if open, negative + * otherwise. */ + MVM_free(b); + MVM_free(a); + /* If any of these fail there is nothing + * further to do, since we're already failing */ + if (in_fd >= 0) + uv_fs_close(tc->loop, &req, in_fd, NULL); + if (out_fd >= 0) + uv_fs_close(tc->loop, &req, out_fd, NULL); + /* This function only throws adhoc errors, so the message is for + * progammer eyes only */ + MVM_exception_throw_adhoc(tc, "Failed to copy file: %s", error); + } } + /* rename one file to another. */ void MVM_file_rename(MVMThreadContext *tc, MVMString *src, MVMString *dest) { char * const a = MVM_string_utf8_encode_C_string(tc, src);