cksum now accepts the --raw option to output a raw binary checksum.
No file name or other information is output in this mode.
+ cp, mv, and install now accept the --debug option to
+ print details on how a file is being copied.
+
factor now accepts the --exponents (-h) option to print factors
in the form p^e, rather than repeating the prime p, e times.
they point to, and preserve hard links between source files in the copies.
Equivalent to @option{--no-dereference --preserve=links}.
+@macro optDebugCopy
+@item --debug
+@opindex --debug
+@cindex debugging, copying
+Print extra information to stdout, explaining how files are copied.
+This option implies the @option{--verbose} option.
+@end macro
+@optDebugCopy
+
@item -f
@itemx --force
@opindex -f
attributes. Then create each given directory, setting their owner,
group and mode as given on the command line or to the defaults.
+@optDebugCopy
+
@item -g @var{group}
@itemx --group=@var{group}
@opindex -g
@optBackup
+@optDebugCopy
+
@item -f
@itemx --force
@opindex -f
static char const *top_level_src_name;
static char const *top_level_dst_name;
+enum copy_debug_val
+ {
+ COPY_DEBUG_UNKNOWN,
+ COPY_DEBUG_NO,
+ COPY_DEBUG_YES,
+ COPY_DEBUG_EXTERNAL,
+ COPY_DEBUG_AVOIDED,
+ COPY_DEBUG_UNSUPPORTED,
+ };
+
+/* debug info about the last file copy. */
+static struct copy_debug
+{
+ enum copy_debug_val offload;
+ enum copy_debug_val reflink;
+ enum copy_debug_val sparse_detection;
+} copy_debug;
+
+static const char*
+copy_debug_string (enum copy_debug_val debug_val)
+{
+ switch (debug_val)
+ {
+ case COPY_DEBUG_NO: return "no";
+ case COPY_DEBUG_YES: return "yes";
+ case COPY_DEBUG_AVOIDED: return "avoided";
+ case COPY_DEBUG_UNSUPPORTED: return "unsupported";
+ default: return "unknown";
+ }
+}
+
+static const char*
+copy_debug_sparse_string (enum copy_debug_val debug_val)
+{
+ switch (debug_val)
+ {
+ case COPY_DEBUG_NO: return "no";
+ case COPY_DEBUG_YES: return "zeros";
+ case COPY_DEBUG_EXTERNAL: return "SEEK_HOLE";
+ default: return "unknown";
+ }
+}
+
+/* Print --debug output on standard output. */
+static void
+emit_debug (const struct cp_options *x)
+{
+ if (! x->hard_link && ! x->symbolic_link)
+ printf ("copy offload: %s, reflink: %s, sparse detection: %s\n",
+ copy_debug_string (copy_debug.offload),
+ copy_debug_string (copy_debug.reflink),
+ copy_debug_sparse_string (copy_debug.sparse_detection));
+}
+
#ifndef DEV_FD_MIGHT_BE_CHR
# define DEV_FD_MIGHT_BE_CHR false
#endif
*last_write_made_hole = false;
*total_n_read = 0;
+ if (copy_debug.sparse_detection == COPY_DEBUG_UNKNOWN)
+ copy_debug.sparse_detection = hole_size ? COPY_DEBUG_YES : COPY_DEBUG_NO;
+
/* If not looking for holes, use copy_file_range if functional,
but don't use if reflink disallowed as that may be implicit. */
if (!hole_size && allow_reflink)
input file seems empty. */
if (*total_n_read == 0)
break;
+ copy_debug.offload = COPY_DEBUG_YES;
return true;
}
if (n_copied < 0)
{
+ copy_debug.offload = COPY_DEBUG_UNSUPPORTED;
+
if (is_CLONENOTSUP (errno))
break;
return false;
}
}
+ copy_debug.offload = COPY_DEBUG_YES;
max_n_read -= n_copied;
*total_n_read += n_copied;
}
+ else
+ copy_debug.offload = COPY_DEBUG_AVOIDED;
+
bool make_hole = false;
off_t psize = 0;
off_t dest_pos = 0;
bool wrote_hole_at_eof = true;
+ copy_debug.sparse_detection = COPY_DEBUG_EXTERNAL;
+
while (0 <= ext_start)
{
off_t ext_end = lseek (src_fd, ext_start, SEEK_HOLE);
&& unlinkat (dst_dirfd, dst_relname, 0) != 0 && errno != ENOENT)
error (0, errno, _("cannot remove %s"), quoteaf (dst_name));
+ if (! transient_failure)
+ copy_debug.reflink = COPY_DEBUG_UNSUPPORTED;
+
if (reflink_mode == REFLINK_ALWAYS || transient_failure)
return false;
bool data_copy_required = x->data_copy_required;
bool preserve_xattr = USE_XATTR & x->preserve_xattr;
+ copy_debug.offload = COPY_DEBUG_UNKNOWN;
+ copy_debug.reflink = x->reflink_mode ? COPY_DEBUG_UNKNOWN : COPY_DEBUG_NO;
+ copy_debug.sparse_detection = COPY_DEBUG_UNKNOWN;
+
source_desc = open (src_name,
(O_RDONLY | O_BINARY
| (x->dereference == DEREF_NEVER ? O_NOFOLLOW : 0)));
}
if (s == 0)
{
+ copy_debug.reflink = COPY_DEBUG_YES;
+
/* Update the clone's timestamps and permissions
as needed. */
goto close_src_desc;
}
}
+ else
+ copy_debug.reflink = COPY_DEBUG_AVOIDED;
+ }
+ else if (data_copy_required && x->reflink_mode)
+ {
+ if (! CLONE_NOOWNERCOPY)
+ copy_debug.reflink = COPY_DEBUG_AVOIDED;
}
#endif
if (data_copy_required && x->reflink_mode)
{
if (clone_file (dest_desc, source_desc) == 0)
- data_copy_required = false;
+ {
+ data_copy_required = false;
+ copy_debug.reflink = COPY_DEBUG_YES;
+ }
else
{
if (! handle_clone_fail (dst_dirfd, dst_relname, src_name, dst_name,
return_val = false;
goto close_src_and_dst_desc;
}
+
+ /* Output debug info for data copying operations. */
+ if (x->debug)
+ emit_debug (x);
}
if (x->preserve_timestamps)
/* If true, display the names of the files before copying them. */
bool verbose;
+ /* If true, display details of how files were copied. */
+ bool debug;
+
/* If true, stdin is a tty. */
bool stdin_tty;
{
ATTRIBUTES_ONLY_OPTION = CHAR_MAX + 1,
COPY_CONTENTS_OPTION,
+ DEBUG_OPTION,
NO_PRESERVE_ATTRIBUTES_OPTION,
PARENTS_OPTION,
PRESERVE_ATTRIBUTES_OPTION,
{"attributes-only", no_argument, NULL, ATTRIBUTES_ONLY_OPTION},
{"backup", optional_argument, NULL, 'b'},
{"copy-contents", no_argument, NULL, COPY_CONTENTS_OPTION},
+ {"debug", no_argument, NULL, DEBUG_OPTION},
{"dereference", no_argument, NULL, 'L'},
{"force", no_argument, NULL, 'f'},
{"interactive", no_argument, NULL, 'i'},
-b like --backup but does not accept an argument\n\
--copy-contents copy contents of special files when recursive\n\
-d same as --no-dereference --preserve=links\n\
+"), stdout);
+ fputs (_("\
+ --debug explain how a file is copied. Implies -v\n\
"), stdout);
fputs (_("\
-f, --force if an existing destination file cannot be\n\
x.data_copy_required = false;
break;
+ case DEBUG_OPTION:
+ x.debug = x.verbose = true;
+ break;
+
case COPY_CONTENTS_OPTION:
copy_contents = true;
break;
non-character as a pseudo short option, starting with CHAR_MAX + 1. */
enum
{
- PRESERVE_CONTEXT_OPTION = CHAR_MAX + 1,
+ DEBUG_OPTION = CHAR_MAX + 1,
+ PRESERVE_CONTEXT_OPTION,
STRIP_PROGRAM_OPTION
};
{"backup", optional_argument, NULL, 'b'},
{"compare", no_argument, NULL, 'C'},
{GETOPT_SELINUX_CONTEXT_OPTION_DECL},
+ {"debug", no_argument, NULL, DEBUG_OPTION},
{"directory", no_argument, NULL, 'd'},
{"group", required_argument, NULL, 'g'},
{"mode", required_argument, NULL, 'm'},
-D create all leading components of DEST except the last,\n\
or all components of --target-directory,\n\
then copy SOURCE to DEST\n\
+"), stdout);
+ fputs (_("\
+ --debug explain how a file is copied. Implies -v\n\
+"), stdout);
+ fputs (_("\
-g, --group=GROUP set group ownership, instead of process' current group\n\
-m, --mode=MODE set permission mode (as in chmod), instead of rwxr-xr-x\n\
-o, --owner=OWNER set ownership (super-user only)\n\
-S, --suffix=SUFFIX override the usual backup suffix\n\
-t, --target-directory=DIRECTORY copy all SOURCE arguments into DIRECTORY\n\
-T, --no-target-directory treat DEST as a normal file\n\
- -v, --verbose print the name of each directory as it is created\n\
+"), stdout);
+ fputs (_("\
+ -v, --verbose print the name of each created file or directory\n\
"), stdout);
fputs (_("\
--preserve-context preserve SELinux security context\n\
signal (SIGCHLD, SIG_DFL);
#endif
break;
+ case DEBUG_OPTION:
+ x.debug = x.verbose = true;
+ break;
case STRIP_PROGRAM_OPTION:
strip_program = xstrdup (optarg);
strip_program_specified = true;
non-character as a pseudo short option, starting with CHAR_MAX + 1. */
enum
{
- NO_COPY_OPTION = CHAR_MAX + 1,
+ DEBUG_OPTION = CHAR_MAX + 1,
+ NO_COPY_OPTION,
STRIP_TRAILING_SLASHES_OPTION
};
{
{"backup", optional_argument, NULL, 'b'},
{"context", no_argument, NULL, 'Z'},
+ {"debug", no_argument, NULL, DEBUG_OPTION},
{"force", no_argument, NULL, 'f'},
{"interactive", no_argument, NULL, 'i'},
{"no-clobber", no_argument, NULL, 'n'},
--backup[=CONTROL] make a backup of each existing destination file\
\n\
-b like --backup but does not accept an argument\n\
+"), stdout);
+ fputs (_("\
+ --debug explain how a file is copied. Implies -v\n\
+"), stdout);
+ fputs (_("\
-f, --force do not prompt before overwriting\n\
-i, --interactive prompt before overwrite\n\
-n, --no-clobber do not overwrite an existing file\n\
case 'n':
x.interactive = I_ALWAYS_NO;
break;
+ case DEBUG_OPTION:
+ x.debug = x.verbose = true;
+ break;
case NO_COPY_OPTION:
x.no_copy = true;
break;
--- /dev/null
+#!/bin/sh
+# Ensure that cp --debug works as documented
+
+# Copyright (C) 2023 Free Software Foundation, Inc.
+
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+
+# You should have received a copy of the GNU General Public License
+# along with this program. If not, see <https://www.gnu.org/licenses/>.
+
+. "${srcdir=.}/tests/init.sh"; path_prepend_ ./src
+print_ver_ cp
+
+touch file || framework_failure_
+cp --debug file file.cp >cp.out || fail=1
+grep 'copy offload:.*reflink:.*sparse detection:' cp.out || fail=1
+cp --debug --attributes-only file file.cp >cp.out || fail=1
+returns_ 1 grep 'copy offload:.*reflink:.*sparse detection:' cp.out || fail=1
+
+Exit $fail
tests/cp/cp-i.sh \
tests/cp/cp-mv-backup.sh \
tests/cp/cp-parents.sh \
+ tests/cp/debug.sh \
tests/cp/deref-slink.sh \
tests/cp/dir-rm-dest.sh \
tests/cp/dir-slash.sh \