Skip to content

Commit af2fba8

Browse files
committed
cp: use lstat for destination check to support LD_PRELOAD tests
1 parent 130f780 commit af2fba8

File tree

4 files changed

+103
-2
lines changed

4 files changed

+103
-2
lines changed

bench_skip_buffer.sh

Lines changed: 91 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,91 @@
1+
#!/bin/bash
2+
# Benchmark: Compare old vs new dd skip implementation
3+
#
4+
# OLD: Uses io::copy + BufReader (internal 8KB chunks regardless of ibs)
5+
# NEW: Uses direct read loop with ibs-sized buffer (matches GNU dd)
6+
7+
set -e
8+
9+
GNU_DD="/bin/dd"
10+
OLD_DD="/tmp/dd_old"
11+
NEW_DD="/tmp/dd_new"
12+
13+
echo "=============================================="
14+
echo "DD Skip Buffer Implementation Comparison"
15+
echo "=============================================="
16+
echo ""
17+
echo "Comparing three implementations:"
18+
echo " GNU dd: Reference implementation"
19+
echo " OLD: io::copy + BufReader (before this PR)"
20+
echo " NEW: Direct read with ibs-sized buffer (this PR)"
21+
echo ""
22+
23+
count_stdin_reads() {
24+
local dd_cmd="$1"
25+
local ibs="$2"
26+
local skip_bytes=$((10 * 1024 * 1024)) # 10MB
27+
local skip_blocks=$((skip_bytes / ibs))
28+
cat /dev/zero | strace -e read "$dd_cmd" ibs=$ibs skip=$skip_blocks count=1 of=/dev/null 2>&1 | grep -c "^read(0"
29+
}
30+
31+
show_read_request() {
32+
local dd_cmd="$1"
33+
local ibs="$2"
34+
local skip_bytes=$((1 * 1024 * 1024)) # 1MB
35+
local skip_blocks=$((skip_bytes / ibs))
36+
# Show the size requested in the read() call
37+
cat /dev/zero | strace -e read "$dd_cmd" ibs=$ibs skip=$skip_blocks count=1 of=/dev/null 2>&1 | grep "^read(0" | head -1 | sed 's/.*,\s*\([0-9]*\)).*/\1/'
38+
}
39+
40+
echo "=============================================="
41+
echo "Test 1: Read Request Size (what size buffer is used)"
42+
echo "=============================================="
43+
echo ""
44+
echo "This shows the 3rd argument to read() - the buffer size requested."
45+
echo "GNU dd uses ibs. OLD used fixed size. NEW should match GNU."
46+
echo ""
47+
48+
printf "%-12s %-15s %-15s %-15s\n" "ibs" "GNU dd" "OLD (before)" "NEW (after)"
49+
printf "%-12s %-15s %-15s %-15s\n" "---" "------" "-----------" "----------"
50+
51+
for ibs in 512 8192 1048576; do
52+
gnu_size=$(show_read_request "$GNU_DD" "$ibs")
53+
old_size=$(show_read_request "$OLD_DD" "$ibs")
54+
new_size=$(show_read_request "$NEW_DD" "$ibs")
55+
printf "%-12s %-15s %-15s %-15s\n" "$ibs" "$gnu_size" "$old_size" "$new_size"
56+
done
57+
58+
echo ""
59+
echo "=============================================="
60+
echo "Test 2: Syscall Count (skipping 10MB)"
61+
echo "=============================================="
62+
echo ""
63+
echo "Fewer syscalls = more efficient (for large ibs)."
64+
echo "More syscalls = correct behavior (for small ibs)."
65+
echo ""
66+
67+
printf "%-12s %-10s %-12s %-15s %-15s\n" "ibs" "Expected" "GNU dd" "OLD (before)" "NEW (after)"
68+
printf "%-12s %-10s %-12s %-15s %-15s\n" "---" "--------" "------" "-----------" "----------"
69+
70+
for ibs in 512 8192 65536; do
71+
expected=$((10 * 1024 * 1024 / ibs))
72+
gnu_count=$(count_stdin_reads "$GNU_DD" "$ibs")
73+
old_count=$(count_stdin_reads "$OLD_DD" "$ibs")
74+
new_count=$(count_stdin_reads "$NEW_DD" "$ibs")
75+
printf "%-12s %-10s %-12s %-15s %-15s\n" "$ibs" "~$expected" "$gnu_count" "$old_count" "$new_count"
76+
done
77+
78+
echo ""
79+
echo "=============================================="
80+
echo "Analysis"
81+
echo "=============================================="
82+
echo ""
83+
echo "OLD behavior (io::copy + BufReader):"
84+
echo " - Always used ~8KB internal buffer regardless of ibs"
85+
echo " - ~1,280 syscalls for 10MB regardless of ibs setting"
86+
echo " - Did NOT match GNU dd behavior"
87+
echo ""
88+
echo "NEW behavior (direct read with ibs buffer):"
89+
echo " - Uses ibs-sized buffer as specified by user"
90+
echo " - Syscall count scales with ibs (matches GNU dd)"
91+
echo " - Correctly implements dd semantics"

src/uu/cp/src/cp.rs

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1390,8 +1390,7 @@ pub fn copy(sources: &[PathBuf], target: &Path, options: &Options) -> CopyResult
13901390
let dest = construct_dest_path(source, target, target_type, options)
13911391
.unwrap_or_else(|_| target.to_path_buf());
13921392

1393-
if fs::metadata(&dest).is_ok()
1394-
&& !fs::symlink_metadata(&dest)?.file_type().is_symlink()
1393+
if FileInformation::from_path(&dest, false).is_ok_and(|info| !info.is_symlink())
13951394
// if both `source` and `dest` are symlinks, it should be considered as an overwrite.
13961395
|| fs::metadata(source).is_ok()
13971396
&& fs::symlink_metadata(source)?.file_type().is_symlink()

src/uucore/src/lib/features/fs.rs

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -166,6 +166,16 @@ impl FileInformation {
166166
#[cfg(any(target_os = "netbsd", not(target_pointer_width = "64")))]
167167
return self.0.st_ino.into();
168168
}
169+
170+
#[cfg(unix)]
171+
pub fn is_symlink(&self) -> bool {
172+
(self.0.st_mode as mode_t & S_IFMT) == S_IFLNK
173+
}
174+
175+
#[cfg(windows)]
176+
pub fn is_symlink(&self) -> bool {
177+
(self.0.file_attributes() & windows_sys::Win32::Storage::FileSystem::FILE_ATTRIBUTE_REPARSE_POINT) != 0
178+
}
169179
}
170180

171181
#[cfg(unix)]

util/fetch-gnu.sh

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ curl -L "${repo}/releases/download/v${ver}/coreutils-${ver}.tar.xz" | tar --stri
77
curl -L ${repo}/raw/refs/heads/master/tests/mv/hardlink-case.sh > tests/mv/hardlink-case.sh
88
curl -L ${repo}/raw/refs/heads/master/tests/mkdir/writable-under-readonly.sh > tests/mkdir/writable-under-readonly.sh
99
curl -L ${repo}/raw/refs/heads/master/tests/cp/cp-mv-enotsup-xattr.sh > tests/cp/cp-mv-enotsup-xattr.sh #spell-checker:disable-line
10+
curl -L ${repo}/raw/refs/heads/master/tests/cp/nfs-removal-race.sh > tests/cp/nfs-removal-race.sh
1011
curl -L ${repo}/raw/refs/heads/master/tests/csplit/csplit-io-err.sh > tests/csplit/csplit-io-err.sh
1112
# Avoid incorrect PASS
1213
curl -L ${repo}/raw/refs/heads/master/tests/runcon/runcon-compute.sh > tests/runcon/runcon-compute.sh

0 commit comments

Comments
 (0)