# SPDX-License-Identifier: BSD-2-Clause # # Copyright (c) 2026 voidanix # MD_DEV_FILE="md.dev" alloc_zoned_md() { atf_check truncate -s 1025m backing_file md=$(mdconfig -a -t vnode -f backing_file) || \ atf_fail "mdconfig -a failed" echo ${md} > $MD_DEV_FILE } # Print the number of zones matching a report option. zone_count() { zonectl -d /dev/${md}.zoned -c rz -o $1 -P summary | \ awk '/zones,/ {print $1}' } zoned_md_cleanup() { if [ -f "$MD_DEV_FILE" ]; then read md < $MD_DEV_FILE [ -c /dev/${md}.zoned ] && \ gzoned destroy ${md}.zoned 2>/dev/null mdconfig -d -u ${md} 2>/dev/null fi true } atf_test_case report_filter cleanup report_filter_head() { atf_set "descr" "zonectl -o only reports zones matching the filter" atf_set "require.user" "root" atf_set "require.kmods" "g_md g_zoned" } report_filter_body() { alloc_zoned_md atf_check gzoned create -s 256m -c 0-1 ${md} atf_check_equal "2" \ "$(zonectl -d /dev/${md}.zoned -c rz -o nonwp -P summary | \ awk '/zones,/ {print $1}')" atf_check_equal "2" \ "$(zonectl -d /dev/${md}.zoned -c rz -o empty -P summary | \ awk '/zones,/ {print $1}')" atf_check_equal "0" \ "$(zonectl -d /dev/${md}.zoned -c rz -o full -P summary | \ awk '/zones,/ {print $1}')" } report_filter_cleanup() { zoned_md_cleanup } atf_test_case report_zones cleanup report_zones_head() { atf_set "descr" "zonectl -c rz reports every zone" atf_set "require.user" "root" atf_set "require.kmods" "g_md g_zoned" } report_zones_body() { alloc_zoned_md atf_check gzoned create -s 256m ${md} atf_check -o match:"^4 zones," \ zonectl -d /dev/${md}.zoned -c rz -P summary atf_check_equal "4" \ "$(zonectl -d /dev/${md}.zoned -c rz -P script | \ awk 'END {print NR}')" } report_zones_cleanup() { zoned_md_cleanup } atf_test_case report_starting_lba cleanup report_starting_lba_head() { atf_set "descr" "zonectl -c rz -l reports zones from a starting LBA" atf_set "require.user" "root" atf_set "require.kmods" "g_md g_zoned" } report_starting_lba_body() { alloc_zoned_md atf_check gzoned create -s 256m ${md} # Only zones 2 and 3 lie at or past LBA 0x100000 (512m). atf_check_equal "2" \ "$(zonectl -d /dev/${md}.zoned -c rz -l 0x100000 -P summary | \ awk '/zones,/ {print $1}')" atf_check_equal "0x100000" \ "$(zonectl -d /dev/${md}.zoned -c rz -l 0x100000 -P script | \ awk -F',' 'NR == 1 {gsub(/ /, "", $1); print $1}')" } report_starting_lba_cleanup() { zoned_md_cleanup } atf_test_case report_params cleanup report_params_head() { atf_set "descr" "zonectl -c params reports the device parameters" atf_set "require.user" "root" atf_set "require.kmods" "g_md g_zoned" } report_params_body() { alloc_zoned_md atf_check gzoned create -s 256m -m 3 ${md} atf_check -o match:"Zone Mode: Host Managed" \ -o match:"URSWRZ.*: Yes" \ -o match:"Open Sequential Write Required Zones: 3" \ zonectl -d /dev/${md}.zoned -c params } report_params_cleanup() { zoned_md_cleanup } atf_test_case open_zone cleanup open_zone_head() { atf_set "descr" "zonectl -c open explicitly opens a zone" atf_set "require.user" "root" atf_set "require.kmods" "g_md g_zoned" } open_zone_body() { alloc_zoned_md atf_check gzoned create -s 256m ${md} atf_check zonectl -d /dev/${md}.zoned -c open -l 0 atf_check_equal "1" "$(zone_count exp_open)" } open_zone_cleanup() { zoned_md_cleanup } atf_test_case close_zone cleanup close_zone_head() { atf_set "descr" "zonectl -c close closes an open zone" atf_set "require.user" "root" atf_set "require.kmods" "g_md g_zoned" } close_zone_body() { alloc_zoned_md atf_check gzoned create -s 256m ${md} atf_check -e ignore dd if=/dev/zero of=/dev/${md}.zoned bs=1m count=1 atf_check_equal "1" "$(zone_count imp_open)" atf_check zonectl -d /dev/${md}.zoned -c close -l 0 atf_check_equal "1" "$(zone_count closed)" atf_check_equal "0" "$(zone_count imp_open)" } close_zone_cleanup() { zoned_md_cleanup } atf_test_case finish_zone cleanup finish_zone_head() { atf_set "descr" "zonectl -c finish transitions a zone to full" atf_set "require.user" "root" atf_set "require.kmods" "g_md g_zoned" } finish_zone_body() { alloc_zoned_md atf_check gzoned create -s 256m ${md} atf_check -e ignore dd if=/dev/zero of=/dev/${md}.zoned bs=1m count=1 atf_check zonectl -d /dev/${md}.zoned -c finish -l 0 atf_check_equal "1" "$(zone_count full)" } finish_zone_cleanup() { zoned_md_cleanup } atf_test_case zone_state_handling cleanup zone_state_handling_head() { atf_set "descr" \ "zone commands follow ZBC spec outside their transition states" atf_set "require.user" "root" atf_set "require.kmods" "g_md g_zoned" } zone_state_handling_body() { alloc_zoned_md atf_check gzoned create -s 256m -c 0 ${md} # Explicitly targeting a conventional zone is the caller's error. atf_check -s not-exit:0 -e ignore \ zonectl -d /dev/${md}.zoned -c finish -l 0 # Closing an empty zone is a no-op. atf_check zonectl -d /dev/${md}.zoned -c close -l 0x80000 atf_check_equal "3" "$(zone_count empty)" # So is opening a full zone: it must stay full. atf_check zonectl -d /dev/${md}.zoned -c finish -l 0x100000 atf_check zonectl -d /dev/${md}.zoned -c open -l 0x100000 atf_check_equal "1" "$(zone_count full)" atf_check_equal "0" "$(zone_count exp_open)" } zone_state_handling_cleanup() { zoned_md_cleanup } atf_init_test_cases() { atf_add_test_case report_filter atf_add_test_case report_zones atf_add_test_case report_starting_lba atf_add_test_case report_params atf_add_test_case open_zone atf_add_test_case close_zone atf_add_test_case finish_zone atf_add_test_case zone_state_handling }