#!/bin/sh
# make sure that dd doesn't allocate memory unnecessarily

# Copyright (C) 2013-2026 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_ dd

# Determine basic amount of memory needed.
echo . > f || framework_failure_
vm=$(get_min_ulimit_v_ timeout 10 dd if=f of=f2 status=none) \
  || skip_ 'shell lacks ulimit, or ASAN enabled'
rm f f2 || framework_failure_

# Ensure dd exits with 1 if memory exhausted
(ulimit -v $vm && returns_ 1 \
 dd if=/dev/null of=/dev/null bs=$(($SSIZE_MAX-1))) || fail=1
# Ensure dd exits with 1 on numeric overflow
(ulimit -v $vm && returns_ 1 \
 dd if=/dev/null of=/dev/null bs=$SIZE_OFLOW) || fail=1
# Ensure dd exits with 1 on invalid number
(ulimit -v $vm && returns_ 1 \
 dd if=/dev/null of=/dev/null bs=0) || fail=1

# count and skip are zero, we don't need to allocate memory
(ulimit -v $vm && dd  bs=30M count=0) || fail=1
(ulimit -v $vm && dd ibs=30M count=0) || fail=1
(ulimit -v $vm && dd obs=30M count=0) || fail=1

check_dd_seek_alloc() {
  local file="$1"
  local buf="$2"
  test "$file" = 'in' && { dd_file=if; dd_op=skip; }
  test "$file" = 'out' && { dd_file=of; dd_op=seek; }
  test "$buf" = 'in' && { dd_buf=ibs; }
  test "$buf" = 'out' && { dd_buf=obs; }
  test "$buf" = 'both' && { dd_buf=bs; }

  # Provide input to the "tape"
  timeout 10 dd count=1 if=/dev/zero of=tape&

  # Allocate buffer and read from the "tape"
  (ulimit -v $(($vm+4000)) \
     && timeout 10 dd $dd_buf=30M $dd_op=1 count=0 $dd_file=tape)
  local ret=$?

  # Be defensive in case the tape reader is blocked for some reason
  test $ret = 124 && framework_failure_

  # This should happen without delay,
  # and is used to ensure we've not multiple writers to the "tape"
  wait

  # We want the "tape" reader to fail iff allocating
  # a large buffer corresponding to the file being read
  case "$file$buf" in
    inout|outin) test $ret = 0;;
    *) test $ret != 0;;
  esac
}

# Use a fifo for which seek fails, but read does not.
# For non seekable output we need to allocate a buffer
# when simulating seeking with a read.
if mkfifo tape; then
  for file in 'in' 'out'; do
    for buf in 'both' 'in' 'out'; do
      check_dd_seek_alloc "$file" "$buf" || fail=1
    done
  done
fi

Exit $fail
