From 6326a796ee80eb0169f782541331dbb3565cfeff Mon Sep 17 00:00:00 2001 From: Yury Luneff Date: Tue, 20 Oct 2026 10:37:47 +0300 Subject: [PATCH 1/8] cli/dd: Detect input size also when size= is given size= turned input size detection off, so dd from an image, a file or a block device with size= always failed with "Input seek position is beyond end of input", after the output image was already created or resized. Always detect the input size: size= only sets the output size. Also check the input seek position before opening the output. --- src/cmd/cli_dd.cpp | 52 ++++++++++++++++++++-------------------------- tests/test_dd.sh | 19 +++++++++++++++++ 2 files changed, 42 insertions(+), 29 deletions(-) diff --git a/src/cmd/cli_dd.cpp b/src/cmd/cli_dd.cpp index e43a494d..895e5944 100644 --- a/src/cmd/cli_dd.cpp +++ b/src/cmd/cli_dd.cpp @@ -37,7 +37,6 @@ struct dd_in_info_t // in std::string iimg, ifile; bool in_direct = false; - bool detect_size = true; // out cli_result_t result; @@ -68,10 +67,7 @@ struct dd_in_info_t return; } in_granularity = pool_it->second.bitmap_granularity; - if (detect_size) - { - in_size = iwatch->cfg.size; - } + in_size = iwatch->cfg.size; in_seekable = true; return; } @@ -92,30 +88,27 @@ struct dd_in_info_t { ifd = 0; } - if (detect_size) + struct stat st; + if (fstat(ifd, &st) < 0) { - struct stat st; - if (fstat(ifd, &st) < 0) + result = (cli_result_t){ .err = errno, .text = "Failed to stat "+ifile+": "+std::string(strerror(errno)) }; + close(ifd); + ifd = -1; + return; + } + if (S_ISREG(st.st_mode)) + { + in_size = st.st_size; + } + else if (S_ISBLK(st.st_mode)) + { + if (ioctl(ifd, BLKGETSIZE64, &in_size) < 0) { - result = (cli_result_t){ .err = errno, .text = "Failed to stat "+ifile+": "+std::string(strerror(errno)) }; + result = (cli_result_t){ .err = errno, .text = "Failed to get "+ifile+" size: "+std::string(strerror(errno)) }; close(ifd); ifd = -1; return; } - if (S_ISREG(st.st_mode)) - { - in_size = st.st_size; - } - else if (S_ISBLK(st.st_mode)) - { - if (ioctl(ifd, BLKGETSIZE64, &in_size) < 0) - { - result = (cli_result_t){ .err = errno, .text = "Failed to get "+ifile+" size: "+std::string(strerror(errno)) }; - close(ifd); - ifd = -1; - return; - } - } } in_seekable = false; if (lseek(ifd, 1, SEEK_SET) == (off_t)1) @@ -848,6 +841,13 @@ struct cli_dd_t state = 100; return; } + if (iinfo.in_seekable && iseek >= iinfo.in_size) + { + iinfo.close_input(parent); + result = (cli_result_t){ .err = EINVAL, .text = "Input seek position is beyond end of input" }; + state = 100; + return; + } if (!oinfo.out_size) { oinfo.out_size = oseek + (iinfo.in_seekable && (!bytelimit || iinfo.in_size-iseek < bytelimit) ? iinfo.in_size-iseek : bytelimit); @@ -871,11 +871,6 @@ resume_2: goto close_end; } // Copy data - if (iinfo.in_seekable && iseek >= iinfo.in_size) - { - result = (cli_result_t){ .err = EINVAL, .text = "Input seek position is beyond end of input" }; - goto close_end; - } if (!iinfo.iwatch && !iinfo.in_seekable && iseek) { // Read and ignore some data from input @@ -1003,7 +998,6 @@ std::function cli_tool_t::start_dd(json11::Json cfg) dd->end_status = false; else if (cfg["status"] == "progress") progress = true; - dd->iinfo.detect_size = cfg["size"].is_null(); dd->oinfo.out_size = parse_size(cfg["size"].as_string()); dd->conv = explode_json(",", cfg["conv"]); dd->iflag = explode_json(",", cfg["iflag"]); diff --git a/tests/test_dd.sh b/tests/test_dd.sh index 36366e28..ea6607a1 100755 --- a/tests/test_dd.sh +++ b/tests/test_dd.sh @@ -24,4 +24,23 @@ $VITASTOR_CLI dd iimg=testimg2 iodepth=4 bs=1M count=128 > ./testdata/testfile1 diff ./testdata/testfile ./testdata/testfile1 rm ./testdata/testfile1 +# size= sets the output size, the input size is still detected +$VITASTOR_CLI dd iimg=testimg oimg=testimg3 iodepth=4 size=192M +$VITASTOR_CLI dd if=./testdata/testfile oimg=testimg4 iodepth=4 size=192M +for img in testimg3 testimg4; do + size=$($VITASTOR_CLI ls --json | jq -r '.[] | select(.name == "'$img'") | .size') + [[ "$size" = $((192*1024*1024)) ]] || format_error "dd size=192M created $img with size $size" + $VITASTOR_CLI dd iimg=$img iodepth=4 bs=1M count=128 > ./testdata/testfile1 + diff ./testdata/testfile ./testdata/testfile1 + rm ./testdata/testfile1 +done + +# skip beyond the end of input fails before the output image is created +rc=0 +$VITASTOR_CLI dd iimg=testimg oimg=testimg5 bs=1M skip=200 count=1 || rc=$? +[[ $rc = 22 ]] || format_error "dd skip beyond the end of input returned $rc instead of EINVAL" +if $VITASTOR_CLI ls --json | jq -e '.[] | select(.name == "testimg5")'; then + format_error "dd skip beyond the end of input created the output image" +fi + format_green OK -- 2.47.3 From 1c0cc44e32a04359eb9a769012d53a7deed9f992 Mon Sep 17 00:00:00 2001 From: Yury Luneff Date: Tue, 20 Oct 2026 10:39:28 +0300 Subject: [PATCH 2/8] cli/dd: Make status=none also suppress the progress output status=none only dropped the final statistics when --progress 0 was also given; with the default --progress 1 dd still printed the progress line and the statistics. Turn progress off with status=none, so only errors are printed, like in GNU dd. --json still returns the result. --- src/cmd/cli_dd.cpp | 3 +++ tests/test_dd.sh | 6 ++++++ 2 files changed, 9 insertions(+) diff --git a/src/cmd/cli_dd.cpp b/src/cmd/cli_dd.cpp index 895e5944..7602b7d7 100644 --- a/src/cmd/cli_dd.cpp +++ b/src/cmd/cli_dd.cpp @@ -995,7 +995,10 @@ std::function cli_tool_t::start_dd(json11::Json cfg) if (!dd->iodepth) dd->iodepth = 4; if (cfg["status"] == "none") + { dd->end_status = false; + progress = false; + } else if (cfg["status"] == "progress") progress = true; dd->oinfo.out_size = parse_size(cfg["size"].as_string()); diff --git a/tests/test_dd.sh b/tests/test_dd.sh index ea6607a1..5cf6a363 100755 --- a/tests/test_dd.sh +++ b/tests/test_dd.sh @@ -43,4 +43,10 @@ if $VITASTOR_CLI ls --json | jq -e '.[] | select(.name == "testimg5")'; then format_error "dd skip beyond the end of input created the output image" fi +# status=none prints nothing, also with the default --progress 1 +$VITASTOR_CLI dd iimg=testimg of=./testdata/testfile1 iodepth=4 status=none 2>./testdata/dd.err +[[ ! -s ./testdata/dd.err ]] || format_error "dd status=none printed: $(cat ./testdata/dd.err)" +diff ./testdata/testfile ./testdata/testfile1 +rm ./testdata/testfile1 + format_green OK -- 2.47.3 From c040b3f98bf53609f8045b74cbb019f2c8c6ddec Mon Sep 17 00:00:00 2001 From: Yury Luneff Date: Tue, 20 Oct 2026 10:40:37 +0300 Subject: [PATCH 3/8] cli/dd: Implement status=noxfer status=noxfer is listed in the help, but dd ignored it. Make it drop the final transfer statistics like in GNU dd. The progress line still follows --progress and, if it was shown, is cleared at the end instead of being replaced by the statistics. --json still returns them. --- src/cmd/cli_dd.cpp | 10 ++++++++++ tests/test_dd.sh | 15 +++++++++++++++ 2 files changed, 25 insertions(+) diff --git a/src/cmd/cli_dd.cpp b/src/cmd/cli_dd.cpp index 7602b7d7..e5be500a 100644 --- a/src/cmd/cli_dd.cpp +++ b/src/cmd/cli_dd.cpp @@ -388,6 +388,7 @@ struct cli_dd_t uint64_t written_size = 0; uint64_t written_progress = 0; timespec tv_begin = {}, tv_progress = {}; + bool progress_shown = false; int state = 0; int copy_error = 0; int in_waiting = 0, out_waiting = 0; @@ -715,6 +716,12 @@ struct cli_dd_t { return; } + if (end && !end_status && !parent->json_output) + { + if (progress_shown) + fprintf(stderr, "\r\033[K"); + return; + } timespec tv_now; clock_gettime(CLOCK_REALTIME, &tv_now); double sec_delta = ((tv_now.tv_sec - tv_progress.tv_sec) + (double)(tv_now.tv_nsec - tv_progress.tv_nsec)/1000000000.0); @@ -757,6 +764,7 @@ struct cli_dd_t format_size((uint64_t)(delta/sec_delta), true).c_str(), format_size((uint64_t)(written_size/sec_total), true).c_str() ); + progress_shown = true; } } @@ -999,6 +1007,8 @@ std::function cli_tool_t::start_dd(json11::Json cfg) dd->end_status = false; progress = false; } + else if (cfg["status"] == "noxfer") + dd->end_status = false; else if (cfg["status"] == "progress") progress = true; dd->oinfo.out_size = parse_size(cfg["size"].as_string()); diff --git a/tests/test_dd.sh b/tests/test_dd.sh index 5cf6a363..6bfef2e4 100755 --- a/tests/test_dd.sh +++ b/tests/test_dd.sh @@ -49,4 +49,19 @@ $VITASTOR_CLI dd iimg=testimg of=./testdata/testfile1 iodepth=4 status=none 2>./ diff ./testdata/testfile ./testdata/testfile1 rm ./testdata/testfile1 +# status=noxfer drops the final statistics line +$VITASTOR_CLI --progress 0 dd iimg=testimg of=./testdata/testfile1 iodepth=4 status=noxfer 2>./testdata/dd.err +[[ ! -s ./testdata/dd.err ]] || format_error "dd status=noxfer printed: $(cat ./testdata/dd.err)" +rm ./testdata/testfile1 +# with --progress 1 the progress line, if it was shown at all, is cleared at the end +clear_line=$'\r\033[K' +for count in 128 4; do + $VITASTOR_CLI dd iimg=testimg of=./testdata/testfile1 iodepth=4 bs=1M count=$count status=noxfer 2>./testdata/dd.err + if [[ $(wc -l < ./testdata/dd.err) != 0 || ( -s ./testdata/dd.err && "$(cat ./testdata/dd.err)" != *copied*"$clear_line" ) ]]; then + format_error "dd status=noxfer count=$count printed: $(cat -v ./testdata/dd.err)" + fi + head -c ${count}M ./testdata/testfile | cmp - ./testdata/testfile1 + rm ./testdata/testfile1 +done + format_green OK -- 2.47.3 From 0618b22013eba343a9799a872cc13b6d8f1f4824 Mon Sep 17 00:00:00 2001 From: Yury Luneff Date: Tue, 20 Oct 2026 10:41:27 +0300 Subject: [PATCH 4/8] cli/dd: Fix oflag=append oflag=append replaced O_RDWR with O_APPEND, so the output file was opened read-only and every write failed with EBADF. Open it with O_WRONLY|O_APPEND. An appending output is not seekable, which also skipped the final fsync: skip it only for outputs that can't be synced (anything but regular files and block devices). Refuse conv=trunc with oflag=append, the two contradict each other. --- src/cmd/cli_dd.cpp | 19 +++++++++++++++++-- tests/test_dd.sh | 13 +++++++++++++ 2 files changed, 30 insertions(+), 2 deletions(-) diff --git a/src/cmd/cli_dd.cpp b/src/cmd/cli_dd.cpp index e5be500a..77b6e4cc 100644 --- a/src/cmd/cli_dd.cpp +++ b/src/cmd/cli_dd.cpp @@ -281,7 +281,7 @@ resume_2: } if (ofile != "") { - ofd = open(ofile.c_str(), (out_direct ? O_DIRECT : 0) | (out_append ? O_APPEND : O_RDWR) | (out_create ? O_CREAT : 0), 0666); + ofd = open(ofile.c_str(), (out_direct ? O_DIRECT : 0) | (out_append ? O_WRONLY|O_APPEND : O_RDWR) | (out_create ? O_CREAT : 0), 0666); if (ofd < 0) { result = (cli_result_t){ .err = errno, .text = "Failed to open "+ofile+": "+std::string(strerror(errno)) }; @@ -307,6 +307,16 @@ resume_2: out_seekable = true; lseek(ofd, 0, SEEK_SET); } + struct stat st; + if (fstat(ofd, &st) < 0) + { + result = (cli_result_t){ .err = errno, .text = "Failed to stat "+(ofile != "" ? ofile : "stdout")+": "+std::string(strerror(errno)) }; + return true; + } + if (!S_ISREG(st.st_mode) && !S_ISBLK(st.st_mode)) + { + end_fsync = false; + } return true; } @@ -827,6 +837,12 @@ struct cli_dd_t state = 100; return; } + if (oinfo.ofile != "" && oinfo.out_append && oinfo.out_trunc) + { + result = (cli_result_t){ .err = EINVAL, .text = "conv=trunc and oflag=append can't be used together" }; + state = 100; + return; + } if ((iinfo.iimg != "" ? "i"+iinfo.iimg : "f"+iinfo.ifile) == (oinfo.oimg != "" ? "i"+oinfo.oimg : "f"+oinfo.ofile)) { result = (cli_result_t){ .err = EINVAL, .text = "Input and output image/file can't be equal" }; @@ -892,7 +908,6 @@ resume_2: in_iodepth = iinfo.in_seekable ? iodepth : 1; out_iodepth = oinfo.out_seekable ? iodepth : 1; write_zero = write_zero || !oinfo.out_seekable; - oinfo.end_fsync = oinfo.end_fsync && oinfo.out_seekable; read_offset = 0; read_end = iinfo.in_seekable ? iinfo.in_size-iseek : 0; if (oinfo.out_size && (!read_end || read_end > oinfo.out_size-oseek)) diff --git a/tests/test_dd.sh b/tests/test_dd.sh index 6bfef2e4..beec99ce 100755 --- a/tests/test_dd.sh +++ b/tests/test_dd.sh @@ -64,4 +64,17 @@ for count in 128 4; do rm ./testdata/testfile1 done +# oflag=append appends to the output file +cp ./testdata/over ./testdata/testfile1 +$VITASTOR_CLI dd iimg=testimg of=./testdata/testfile1 iodepth=4 bs=1M count=8 oflag=append +(cat ./testdata/over; head -c 8M ./testdata/testfile) | cmp - ./testdata/testfile1 || format_error "dd oflag=append did not append to the output file" +rm ./testdata/testfile1 +# conv=trunc contradicts oflag=append: fail without touching the output file +cp ./testdata/over ./testdata/testfile1 +rc=0 +$VITASTOR_CLI dd iimg=testimg of=./testdata/testfile1 iodepth=4 bs=1M count=8 oflag=append conv=trunc || rc=$? +[[ $rc = 22 ]] || format_error "dd conv=trunc oflag=append returned $rc instead of EINVAL" +cmp ./testdata/over ./testdata/testfile1 || format_error "dd conv=trunc oflag=append changed the output file" +rm ./testdata/testfile1 + format_green OK -- 2.47.3 From d4d227a838f34efdecedd909eec0405622f2586c Mon Sep 17 00:00:00 2001 From: Yury Luneff Date: Tue, 20 Oct 2026 10:42:25 +0300 Subject: [PATCH 5/8] cli/dd: Copy a pipe input to its end when no count= or size= is given With a non-seekable input and no count= or size=, read_end stayed 0, or was capped at zero bytes by out_size defaulting to seek=, so dd stopped after the first block: "cat file | vitastor-cli dd of=copy" copied 1 MB and exited 0. Leave read_end unlimited then and default out_size only when the input length is known. A new or truncated image still needs size=; conv=trunc on a file still truncates it at the seek offset. --- src/cmd/cli_dd.cpp | 10 +++++----- tests/test_dd.sh | 8 ++++++++ 2 files changed, 13 insertions(+), 5 deletions(-) diff --git a/src/cmd/cli_dd.cpp b/src/cmd/cli_dd.cpp index 77b6e4cc..9bfeeec0 100644 --- a/src/cmd/cli_dd.cpp +++ b/src/cmd/cli_dd.cpp @@ -168,7 +168,7 @@ struct dd_out_info_t return NULL; } - bool open_output(cli_tool_t *parent, int & state, int base_state) + bool open_output(cli_tool_t *parent, int & state, int base_state, uint64_t oseek) { if (state == base_state) goto resume_1; @@ -287,7 +287,7 @@ resume_2: result = (cli_result_t){ .err = errno, .text = "Failed to open "+ofile+": "+std::string(strerror(errno)) }; return true; } - if (out_trunc && ftruncate(ofd, out_size) < 0) + if (out_trunc && ftruncate(ofd, out_size ? out_size : oseek) < 0) { result = (cli_result_t){ .err = errno, .text = "Failed to truncate "+ofile+": "+std::string(strerror(errno)) }; return true; @@ -872,13 +872,13 @@ struct cli_dd_t state = 100; return; } - if (!oinfo.out_size) + if (!oinfo.out_size && (iinfo.in_seekable || bytelimit)) { oinfo.out_size = oseek + (iinfo.in_seekable && (!bytelimit || iinfo.in_size-iseek < bytelimit) ? iinfo.in_size-iseek : bytelimit); } resume_1: resume_2: - if (!oinfo.open_output(parent, state, 1)) + if (!oinfo.open_output(parent, state, 1, oseek)) { return; } @@ -909,7 +909,7 @@ resume_2: out_iodepth = oinfo.out_seekable ? iodepth : 1; write_zero = write_zero || !oinfo.out_seekable; read_offset = 0; - read_end = iinfo.in_seekable ? iinfo.in_size-iseek : 0; + read_end = iinfo.in_seekable ? iinfo.in_size-iseek : UINT64_MAX; if (oinfo.out_size && (!read_end || read_end > oinfo.out_size-oseek)) read_end = oinfo.out_size-oseek; if (bytelimit && (!read_end || read_end > bytelimit)) diff --git a/tests/test_dd.sh b/tests/test_dd.sh index beec99ce..9573ff68 100755 --- a/tests/test_dd.sh +++ b/tests/test_dd.sh @@ -77,4 +77,12 @@ $VITASTOR_CLI dd iimg=testimg of=./testdata/testfile1 iodepth=4 bs=1M count=8 of cmp ./testdata/over ./testdata/testfile1 || format_error "dd conv=trunc oflag=append changed the output file" rm ./testdata/testfile1 +# pipe in without count - copy to the end of input +$VITASTOR_CLI dd iimg=testimg iodepth=4 | $VITASTOR_CLI dd of=./testdata/testfile1 iodepth=4 +diff ./testdata/testfile ./testdata/testfile1 +rm ./testdata/testfile1 +$VITASTOR_CLI dd iimg=testimg iodepth=4 | $VITASTOR_CLI dd of=./testdata/testfile1 iodepth=4 bs=1M seek=4 +(head -c 4M /dev/zero; cat ./testdata/testfile) | cmp - ./testdata/testfile1 || format_error "dd from a pipe with seek= did not copy the whole input" +rm ./testdata/testfile1 + format_green OK -- 2.47.3 From 25ecb001aaaa1664c6509b41edd9348f875507e5 Mon Sep 17 00:00:00 2001 From: Yury Luneff Date: Tue, 20 Oct 2026 10:44:24 +0300 Subject: [PATCH 6/8] cli/dd: Check write alignment before creating the output image The "Unaligned write to Vitastor is not supported" check ran after the output image was created or resized, so a seek= or bs= not aligned to the pool's bitmap granularity failed with EINVAL but left a new or resized image behind. Check it as soon as the granularity is known. --- src/cmd/cli_dd.cpp | 16 +++++++++------- tests/test_dd.sh | 12 ++++++++++++ 2 files changed, 21 insertions(+), 7 deletions(-) diff --git a/src/cmd/cli_dd.cpp b/src/cmd/cli_dd.cpp index 9bfeeec0..e4a48e62 100644 --- a/src/cmd/cli_dd.cpp +++ b/src/cmd/cli_dd.cpp @@ -168,7 +168,7 @@ struct dd_out_info_t return NULL; } - bool open_output(cli_tool_t *parent, int & state, int base_state, uint64_t oseek) + bool open_output(cli_tool_t *parent, int & state, int base_state, uint64_t oseek, uint64_t blocksize) { if (state == base_state) goto resume_1; @@ -205,6 +205,13 @@ struct dd_out_info_t return true; } } + if ((oseek % out_granularity) || (blocksize % out_granularity)) + { + result = (cli_result_t){ .err = EINVAL, .text = "Unaligned write to Vitastor is not supported" }; + parent->cli->st_cli->close_watch(owatch); + owatch = NULL; + return true; + } if (out_size % 4096) { out_size += (4096 - (out_size % 4096)); @@ -878,7 +885,7 @@ struct cli_dd_t } resume_1: resume_2: - if (!oinfo.open_output(parent, state, 1, oseek)) + if (!oinfo.open_output(parent, state, 1, oseek, blocksize)) { return; } @@ -889,11 +896,6 @@ resume_2: state = 100; return; } - if (oinfo.owatch && ((oseek % oinfo.out_granularity) || (blocksize % oinfo.out_granularity))) - { - result = (cli_result_t){ .err = EINVAL, .text = "Unaligned write to Vitastor is not supported" }; - goto close_end; - } // Copy data if (!iinfo.iwatch && !iinfo.in_seekable && iseek) { diff --git a/tests/test_dd.sh b/tests/test_dd.sh index 9573ff68..233d73ae 100755 --- a/tests/test_dd.sh +++ b/tests/test_dd.sh @@ -85,4 +85,16 @@ $VITASTOR_CLI dd iimg=testimg iodepth=4 | $VITASTOR_CLI dd of=./testdata/testfil (head -c 4M /dev/zero; cat ./testdata/testfile) | cmp - ./testdata/testfile1 || format_error "dd from a pipe with seek= did not copy the whole input" rm ./testdata/testfile1 +# unaligned image I/O fails with EINVAL before the output image is created or resized +for args in "if=./testdata/testfile oimg=testimg6 seek=512B" "if=./testdata/testfile oimg=testimg2 seek=512B" "iimg=testimg oimg=testimg7 skip=512B"; do + rc=0 + $VITASTOR_CLI dd iodepth=4 $args || rc=$? + [[ $rc = 22 ]] || format_error "dd $args returned $rc instead of EINVAL" +done +if $VITASTOR_CLI ls --json | jq -e '.[] | select(.name == "testimg6" or .name == "testimg7")'; then + format_error "dd with unaligned image I/O created the output image" +fi +size=$($VITASTOR_CLI ls --json | jq -r '.[] | select(.name == "testimg2") | .size') +[[ "$size" = $((128*1024*1024)) ]] || format_error "dd with unaligned seek resized testimg2 to $size" + format_green OK -- 2.47.3 From 891f7552d1cc895b8ff1f2e9a2826becbb940bb1 Mon Sep 17 00:00:00 2001 From: Yury Luneff Date: Tue, 20 Oct 2026 10:49:38 +0300 Subject: [PATCH 7/8] cli/dd: Read a character device input like a pipe A character device reports st_size 0, so one that supports lseek (e.g. /dev/mem or an MTD device) was taken as an empty seekable input and dd always failed with "Input seek position is beyond end of input". Treat every character device input as non-seekable with unknown size and read it up to count=, size= or its end. /dev/zero and /dev/urandom don't seek and were already read this way. --- src/cmd/cli_dd.cpp | 3 ++- tests/test_dd.sh | 8 ++++++++ 2 files changed, 10 insertions(+), 1 deletion(-) diff --git a/src/cmd/cli_dd.cpp b/src/cmd/cli_dd.cpp index e4a48e62..48588abd 100644 --- a/src/cmd/cli_dd.cpp +++ b/src/cmd/cli_dd.cpp @@ -111,7 +111,8 @@ struct dd_in_info_t } } in_seekable = false; - if (lseek(ifd, 1, SEEK_SET) == (off_t)1) + // a seekable character device has no size (st_size 0), read it like a pipe + if (!S_ISCHR(st.st_mode) && lseek(ifd, 1, SEEK_SET) == (off_t)1) { in_seekable = true; lseek(ifd, 0, SEEK_SET); diff --git a/tests/test_dd.sh b/tests/test_dd.sh index 233d73ae..75ed2647 100755 --- a/tests/test_dd.sh +++ b/tests/test_dd.sh @@ -97,4 +97,12 @@ fi size=$($VITASTOR_CLI ls --json | jq -r '.[] | select(.name == "testimg2") | .size') [[ "$size" = $((128*1024*1024)) ]] || format_error "dd with unaligned seek resized testimg2 to $size" +# a character device is read like a pipe, up to count= or size= +$VITASTOR_CLI dd if=/dev/zero oimg=testimg8 size=32M +size=$($VITASTOR_CLI ls --json | jq -r '.[] | select(.name == "testimg8") | .size') +[[ "$size" = $((32*1024*1024)) ]] || format_error "dd if=/dev/zero size=32M created testimg8 with size $size" +$VITASTOR_CLI dd if=/dev/zero of=./testdata/testfile1 bs=1M count=4 conv=nosparse +head -c 4M /dev/zero | cmp - ./testdata/testfile1 || format_error "dd if=/dev/zero count=4 did not write 4 MiB of zeros" +rm ./testdata/testfile1 + format_green OK -- 2.47.3 From 34b09af052cc7b9a00b645d7f7690fd5be52b4b9 Mon Sep 17 00:00:00 2001 From: Arsenii Ermilov Date: Tue, 20 Oct 2026 11:00:58 +0300 Subject: [PATCH 8/8] cli/dd: Free the skip buffer when reading the input fails skip_read() returned on a read error or EOF without freeing the buffer it allocated, leaking it once per failed dd. --- src/cmd/cli_dd.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/src/cmd/cli_dd.cpp b/src/cmd/cli_dd.cpp index 48588abd..fd052d6f 100644 --- a/src/cmd/cli_dd.cpp +++ b/src/cmd/cli_dd.cpp @@ -425,6 +425,7 @@ struct cli_dd_t auto res = read(fd, buf, blocksize < to_skip ? blocksize : to_skip); if (res <= 0) { + free(buf); return res == 0 ? -EPIPE : -errno; } to_skip -= res; -- 2.47.3