From 2dfc02e67d1f45c184473bbd42c841daa0543cc6 Mon Sep 17 00:00:00 2001 From: omrzk <193513049+omrzk@users.noreply.github.com> Date: Fri, 13 Nov 2026 09:12:43 -0600 Subject: [PATCH] Preserve binary data when copying file streams Copy the bytes returned by fread instead of treating each input chunk as a NUL-terminated string. Preserve the public stream positions and report read and write failures rather than returning success after a partial copy. Add binary boundary, current-position, native pipe error and full-device regression coverage. --- src/copyfile.c | 21 ++++++++--- test/.gitignore | 1 + test/Makefile.am | 1 + test/fcopyfile.c | 92 ++++++++++++++++++++++++++++++++++++++++++++++++ 4 files changed, 110 insertions(+), 5 deletions(-) create mode 100644 test/fcopyfile.c diff --git a/src/copyfile.c b/src/copyfile.c index 2abc6fd..2936daa 100644 --- a/src/copyfile.c +++ b/src/copyfile.c @@ -286,6 +286,8 @@ int movefile(const char *src, const char *dst) int fcopyfile(FILE *src, FILE *dst) { char *buf; + size_t len; + int rc, saved_errno; if (!src || !dst) { errno = EINVAL; @@ -296,12 +298,21 @@ int fcopyfile(FILE *src, FILE *dst) if (!buf) return -1; - while (fgets(buf, BUFSIZ, src)) - fputs(buf, dst); - - free(buf); + rc = 0; + while ((len = fread(buf, 1, BUFSIZ, src)) > 0) { + if (fwrite(buf, 1, len, dst) != len) { + rc = -1; + break; + } + } + if (ferror(src)) + rc = -1; - return 0; + saved_errno = errno; + free(buf); + errno = saved_errno; + + return rc; } /** diff --git a/test/.gitignore b/test/.gitignore index b2ed0a7..7128afe 100644 --- a/test/.gitignore +++ b/test/.gitignore @@ -4,6 +4,7 @@ .deps/ /chomp /copyfile +/fcopyfile /dir /fexist /fisdir diff --git a/test/Makefile.am b/test/Makefile.am index bfa461c..ef46bcc 100644 --- a/test/Makefile.am +++ b/test/Makefile.am @@ -19,6 +19,7 @@ clean-local: TESTS = TESTS += chomp TESTS += copyfile +TESTS += fcopyfile TESTS += dir TESTS += fexist TESTS += fisdir diff --git a/test/fcopyfile.c b/test/fcopyfile.c new file mode 100644 index 0000000..4e2aa8f --- /dev/null +++ b/test/fcopyfile.c @@ -0,0 +1,92 @@ +#include +#include +#include +#include "check.h" + +static void copy_binary(size_t len) +{ + unsigned char data[3 * BUFSIZ + 37], copied[sizeof(data)]; + const char prefix[] = "destination prefix"; + FILE *src = tmpfile(), *dst = tmpfile(); + size_t i; + + fail_unless(src && dst); + for (i = 0; i < sizeof(data); i++) + data[i] = i % 253; + + /* Begin at the current stream positions, preserving the destination prefix. */ + fail_unless(fwrite(prefix, 1, sizeof(prefix), src) == sizeof(prefix)); + fail_unless(fwrite(data, 1, len, src) == len); + fail_unless(fseek(src, sizeof(prefix), SEEK_SET) == 0); + fail_unless(fwrite(prefix, 1, sizeof(prefix), dst) == sizeof(prefix)); + + fail_unless(fcopyfile(src, dst) == 0); + fail_unless(ftell(src) == (long)(sizeof(prefix) + len)); + fail_unless(ftell(dst) == (long)(sizeof(prefix) + len)); + fail_unless(fseek(dst, 0, SEEK_SET) == 0); + fail_unless(fread(copied, 1, sizeof(prefix), dst) == sizeof(prefix)); + fail_unless(memcmp(copied, prefix, sizeof(prefix)) == 0); + fail_unless(fread(copied, 1, sizeof(copied), dst) == len); + fail_unless(memcmp(copied, data, len) == 0); + fail_unless(fgetc(dst) == EOF && feof(dst)); + fail_unless(fclose(src) == 0); + fail_unless(fclose(dst) == 0); +} + +static void read_error(int partial) +{ + const char data[] = "bytes before the read error"; + char copied[sizeof(data)]; + int fd[2]; + FILE *src, *dst = tmpfile(); + + fail_unless(dst && pipe(fd) == 0); + fail_unless(fcntl(fd[0], F_SETFL, O_NONBLOCK) == 0); + if (partial) + fail_unless(write(fd[1], data, sizeof(data)) == sizeof(data)); + src = fdopen(fd[0], "r"); + fail_unless(src); + errno = 0; + fail_unless(fcopyfile(src, dst) == -1); + fail_unless(errno == EAGAIN || errno == EWOULDBLOCK); + fail_unless(ferror(src)); + fail_unless(ftell(dst) == (long)(partial ? sizeof(data) : 0)); + if (partial) { + rewind(dst); + fail_unless(fread(copied, 1, sizeof(copied), dst) == sizeof(data)); + fail_unless(memcmp(copied, data, sizeof(data)) == 0); + } + fclose(src); + fclose(dst); + close(fd[1]); +} + +int main(void) +{ + const size_t lengths[] = { 0, 1, BUFSIZ - 1, BUFSIZ, BUFSIZ + 1, 3 * BUFSIZ + 37 }; + size_t i; + + for (i = 0; i < NELEMS(lengths); i++) + copy_binary(lengths[i]); + read_error(0); + read_error(1); + +#ifdef __linux__ + FILE *src = tmpfile(); + FILE *dst = fopen("/dev/full", "w"); + fail_unless(src && dst); + fail_unless(fputs("data", src) >= 0); + rewind(src); + fail_unless(setvbuf(dst, NULL, _IONBF, 0) == 0); + errno = 0; + fail_unless(fcopyfile(src, dst) == -1); + fail_unless(errno == ENOSPC); + fail_unless(ferror(dst)); + fclose(src); + fclose(dst); +#endif + + errno = 0; + fail_unless(fcopyfile(NULL, NULL) == -1 && errno == EINVAL); + return 0; +}