Hi Reuben, I've added your patch, with a couple of modifications:
- After the refactoring that calls qcopy_acl rather than copy_acl, two error messages are possible instead of one. Added one more GL_COPY_ERR_* value. - In copy-file.h declare the right function name. - In the tests I don't find it a good idea to overwrite the source file with the destination file, because that can break subsequent tests that assume a certain state in the source file. So I added tests for the new function. Not as detailed as what you proposed, but the basic function is tested. - In the tests I also had to drop the test with /dev/full, since - unlike /dev/null - this device does not exist everywhere. Ex.: on OpenBSD, NetBSD. 2012-01-11 Reuben Thomas <r...@sc3d.org> Bruno Haible <br...@clisp.org> copy-file: add error-code-returning variant. * lib/copy-file.h (GL_COPY_ERR_*): New enumeration items. (qcopy_file_preserving): New declaration. * lib/copy-file.c (qcopy_file_preserving): Renamed from copy_file_preserving. Change return type to 'int'. Don't emit an error message here. (copy_file_preserving): New function. * tests/test-copy-file.c: Include <stdlib.h>. (main): Test qcopy_file_preserving if the environment variable NO_STDERR_OUTPUT is set. * tests/test-copy-file-1.sh: Invoke test-copy-file.sh a second time, with NO_STDERR_OUTPUT * tests/test-copy-file-2.sh: Likewise. --- lib/copy-file.c.orig Thu Jan 12 03:00:14 2012 +++ lib/copy-file.c Thu Jan 12 02:07:16 2012 @@ -54,9 +54,10 @@ enum { IO_SIZE = 32 * 1024 }; -void -copy_file_preserving (const char *src_filename, const char *dest_filename) +int +qcopy_file_preserving (const char *src_filename, const char *dest_filename) { + int err = 0; int src_fd; struct stat statbuf; int mode; @@ -64,40 +65,58 @@ char *buf = xmalloc (IO_SIZE); src_fd = open (src_filename, O_RDONLY | O_BINARY); - if (src_fd < 0 || fstat (src_fd, &statbuf) < 0) - error (EXIT_FAILURE, errno, _("error while opening %s for reading"), - quote (src_filename)); + if (src_fd < 0) + { + err = GL_COPY_ERR_OPEN_READ; + goto error; + } + if (fstat (src_fd, &statbuf) < 0) + { + err = GL_COPY_ERR_OPEN_READ; + goto error_src; + } mode = statbuf.st_mode & 07777; dest_fd = open (dest_filename, O_WRONLY | O_CREAT | O_TRUNC | O_BINARY, 0600); if (dest_fd < 0) - error (EXIT_FAILURE, errno, _("cannot open backup file %s for writing"), - quote (dest_filename)); + { + err = GL_COPY_ERR_OPEN_BACKUP_WRITE; + goto error_src; + } /* Copy the file contents. */ for (;;) { size_t n_read = safe_read (src_fd, buf, IO_SIZE); if (n_read == SAFE_READ_ERROR) - error (EXIT_FAILURE, errno, _("error reading %s"), - quote (src_filename)); + { + err = GL_COPY_ERR_READ; + goto error_src_dest; + } if (n_read == 0) break; if (full_write (dest_fd, buf, n_read) < n_read) - error (EXIT_FAILURE, errno, _("error writing %s"), - quote (est_filename)); + { + err = GL_COPY_ERR_WRITE; + goto error_src_dest; + } } free (buf); #if !USE_ACL if (close (dest_fd) < 0) - error (EXIT_FAILURE, errno, _("error writing %s"), quote (dest_filename)); + { + err = GL_COPY_ERR_WRITE; + goto error_src; + } if (close (src_fd) < 0) - error (EXIT_FAILURE, errno, _("error after reading %s"), - quote (src_filename)); + { + err = GL_COPY_ERR_AFTER_READ; + goto error; + } #endif /* Preserve the access and modification times. */ @@ -129,10 +148,11 @@ switch (qcopy_acl (src_filename, src_fd, dest_filename, dest_fd, mode)) { case -2: - error (EXIT_FAILURE, errno, "%s", quote (src_filename)); + err = GL_COPY_ERR_GET_ACL; + goto error_src_dest; case -1: - error (EXIT_FAILURE, errno, _("preserving permissions for %s"), - quote (dest_filename)); + err = GL_COPY_ERR_SET_ACL; + goto error_src_dest; } #else chmod (dest_filename, mode); @@ -140,9 +160,63 @@ #if USE_ACL if (close (dest_fd) < 0) - error (EXIT_FAILURE, errno, _("error writing %s"), quote (dest_filename)); + { + err = GL_COPY_ERR_WRITE; + goto error_src; + } if (close (src_fd) < 0) - error (EXIT_FAILURE, errno, _("error after reading %s"), - quote (src_filename)); + { + err = GL_COPY_ERR_AFTER_READ; + goto error; + } #endif + + return 0; + + error_src_dest: + close (dest_fd); + error_src: + close (src_fd); + error: + return err; +} + +void +copy_file_preserving (const char *src_filename, const char *dest_filename) +{ + switch (qcopy_file_preserving (src_filename, dest_filename)) + { + case 0: + return; + + case GL_COPY_ERR_OPEN_READ: + error (EXIT_FAILURE, errno, _("error while opening %s for reading"), + quote (src_filename)); + + case GL_COPY_ERR_OPEN_BACKUP_WRITE: + error (EXIT_FAILURE, errno, _("cannot open backup file %s for writing"), + quote (dest_filename)); + + case GL_COPY_ERR_READ: + error (EXIT_FAILURE, errno, _("error reading %s"), + quote (src_filename)); + + case GL_COPY_ERR_WRITE: + error (EXIT_FAILURE, errno, _("error writing %s"), + quote (dest_filename)); + + case GL_COPY_ERR_AFTER_READ: + error (EXIT_FAILURE, errno, _("error after reading %s"), + quote (src_filename)); + + case GL_COPY_ERR_GET_ACL: + error (EXIT_FAILURE, errno, "%s", quote (src_filename)); + + case GL_COPY_ERR_SET_ACL: + error (EXIT_FAILURE, errno, _("preserving permissions for %s"), + quote (dest_filename)); + + default: + abort (); + } } --- lib/copy-file.h.orig Thu Jan 12 03:00:15 2012 +++ lib/copy-file.h Thu Jan 12 02:04:45 2012 @@ -21,6 +21,26 @@ #endif +/* Error codes returned by qcopy_file_preserving. */ +enum +{ + GL_COPY_ERR_OPEN_READ = -1, + GL_COPY_ERR_OPEN_BACKUP_WRITE = -2, + GL_COPY_ERR_READ = -3, + GL_COPY_ERR_WRITE = -4, + GL_COPY_ERR_AFTER_READ = -5, + GL_COPY_ERR_GET_ACL = -6, + GL_COPY_ERR_SET_ACL = -7 +}; + +/* Copy a regular file: from src_filename to dest_filename. + The destination file is assumed to be a backup file. + Modification times, owner, group and access permissions are preserved as + far as possible. + Return 0 if successful, otherwise set errno and return one of the error + codes above. */ +extern int qcopy_file_preserving (const char *src_filename, const char *dest_filename); + /* Copy a regular file: from src_filename to dest_filename. The destination file is assumed to be a backup file. Modification times, owner, group and access permissions are preserved as --- tests/test-copy-file-1.sh.orig Thu Jan 12 03:00:15 2012 +++ tests/test-copy-file-1.sh Thu Jan 12 02:15:02 2012 @@ -10,4 +10,11 @@ fi export TMPDIR -exec "${srcdir}/test-copy-file.sh" +"${srcdir}/test-copy-file.sh" +ret1=$? +NO_STDERR_OUTPUT=1 "${srcdir}/test-copy-file.sh" +ret2=$? +case $ret1 in + 77 ) exit $ret2 ;; + * ) exit $ret1 ;; +esac --- tests/test-copy-file-2.sh.orig Thu Jan 12 03:00:15 2012 +++ tests/test-copy-file-2.sh Thu Jan 12 02:15:14 2012 @@ -6,4 +6,11 @@ TMPDIR=`pwd` export TMPDIR -exec "${srcdir}/test-copy-file.sh" +"${srcdir}/test-copy-file.sh" +ret1=$? +NO_STDERR_OUTPUT=1 "${srcdir}/test-copy-file.sh" +ret2=$? +case $ret1 in + 77 ) exit $ret2 ;; + * ) exit $ret1 ;; +esac --- tests/test-copy-file.c.orig Thu Jan 12 03:00:15 2012 +++ tests/test-copy-file.c Thu Jan 12 02:11:56 2012 @@ -20,6 +20,8 @@ #include "copy-file.h" +#include <stdlib.h> + #include "progname.h" #include "macros.h" @@ -28,6 +30,7 @@ { const char *file1; const char *file2; + int null_stderr; set_program_name (argv[0]); @@ -35,8 +38,12 @@ file1 = argv[1]; file2 = argv[2]; + null_stderr = (getenv ("NO_STDERR_OUTPUT") != NULL); - copy_file_preserving (file1, file2); + if (null_stderr) + ASSERT (qcopy_file_preserving (file1, file2) == 0); + else + copy_file_preserving (file1, file2); return 0; }