]> git.ipfire.org Git - thirdparty/kernel/linux.git/commitdiff
selftests: ublk: add stop command with --safe option
authorMing Lei <ming.lei@redhat.com>
Mon, 12 Jan 2026 22:05:02 +0000 (00:05 +0200)
committerJens Axboe <axboe@kernel.dk>
Mon, 12 Jan 2026 22:07:31 +0000 (15:07 -0700)
Add 'stop' subcommand to kublk utility that uses the new
UBLK_CMD_TRY_STOP_DEV command when --safe option is specified.
This allows stopping a device only if it has no active openers,
returning -EBUSY otherwise.

Also add test_generic_16.sh to test the new functionality.

Signed-off-by: Ming Lei <ming.lei@redhat.com>
Signed-off-by: Jens Axboe <axboe@kernel.dk>
tools/testing/selftests/ublk/Makefile
tools/testing/selftests/ublk/kublk.c
tools/testing/selftests/ublk/kublk.h
tools/testing/selftests/ublk/test_generic_16.sh [new file with mode: 0755]

index 036a9f01b4649487be9af7b664c163e7f80f57ad..3a2498089b15c2e88a445bfa3c3b23e31c6f3ebc 100644 (file)
@@ -23,6 +23,7 @@ TEST_PROGS += test_generic_12.sh
 TEST_PROGS += test_generic_13.sh
 TEST_PROGS += test_generic_14.sh
 TEST_PROGS += test_generic_15.sh
+TEST_PROGS += test_generic_16.sh
 
 TEST_PROGS += test_null_01.sh
 TEST_PROGS += test_null_02.sh
index d95937dd616737f07351c8b5db820c6dc294aa46..3472ce7426ba86e1f67c39dac083f1d70a076f49 100644 (file)
@@ -108,6 +108,15 @@ static int ublk_ctrl_stop_dev(struct ublk_dev *dev)
        return __ublk_ctrl_cmd(dev, &data);
 }
 
+static int ublk_ctrl_try_stop_dev(struct ublk_dev *dev)
+{
+       struct ublk_ctrl_cmd_data data = {
+               .cmd_op = UBLK_U_CMD_TRY_STOP_DEV,
+       };
+
+       return __ublk_ctrl_cmd(dev, &data);
+}
+
 static int ublk_ctrl_start_dev(struct ublk_dev *dev,
                int daemon_pid)
 {
@@ -1424,6 +1433,42 @@ static int cmd_dev_del(struct dev_ctx *ctx)
        return 0;
 }
 
+static int cmd_dev_stop(struct dev_ctx *ctx)
+{
+       int number = ctx->dev_id;
+       struct ublk_dev *dev;
+       int ret;
+
+       if (number < 0) {
+               ublk_err("%s: device id is required\n", __func__);
+               return -EINVAL;
+       }
+
+       dev = ublk_ctrl_init();
+       dev->dev_info.dev_id = number;
+
+       ret = ublk_ctrl_get_info(dev);
+       if (ret < 0)
+               goto fail;
+
+       if (ctx->safe_stop) {
+               ret = ublk_ctrl_try_stop_dev(dev);
+               if (ret < 0)
+                       ublk_err("%s: try_stop dev %d failed ret %d\n",
+                                       __func__, number, ret);
+       } else {
+               ret = ublk_ctrl_stop_dev(dev);
+               if (ret < 0)
+                       ublk_err("%s: stop dev %d failed ret %d\n",
+                                       __func__, number, ret);
+       }
+
+fail:
+       ublk_ctrl_deinit(dev);
+
+       return ret;
+}
+
 static int __cmd_dev_list(struct dev_ctx *ctx)
 {
        struct ublk_dev *dev = ublk_ctrl_init();
@@ -1487,6 +1532,7 @@ static int cmd_dev_get_features(void)
                FEAT_NAME(UBLK_F_PER_IO_DAEMON),
                FEAT_NAME(UBLK_F_BUF_REG_OFF_DAEMON),
                FEAT_NAME(UBLK_F_INTEGRITY),
+               FEAT_NAME(UBLK_F_SAFE_STOP_DEV)
        };
        struct ublk_dev *dev;
        __u64 features = 0;
@@ -1616,6 +1662,8 @@ static int cmd_dev_help(char *exe)
 
        printf("%s del [-n dev_id] -a \n", exe);
        printf("\t -a delete all devices -n delete specified device\n\n");
+       printf("%s stop -n dev_id [--safe]\n", exe);
+       printf("\t --safe only stop if device has no active openers\n\n");
        printf("%s list [-n dev_id] -a \n", exe);
        printf("\t -a list all devices, -n list specified device, default -a \n\n");
        printf("%s features\n", exe);
@@ -1653,6 +1701,7 @@ int main(int argc, char *argv[])
                { "pi_offset",          1,      NULL,  0 },
                { "csum_type",          1,      NULL,  0 },
                { "tag_size",           1,      NULL,  0 },
+               { "safe",               0,      NULL,  0 },
                { 0, 0, 0, 0 }
        };
        const struct ublk_tgt_ops *ops = NULL;
@@ -1760,6 +1809,8 @@ int main(int argc, char *argv[])
                        }
                        if (!strcmp(longopts[option_idx].name, "tag_size"))
                                ctx.tag_size = strtoul(optarg, NULL, 0);
+                       if (!strcmp(longopts[option_idx].name, "safe"))
+                               ctx.safe_stop = 1;
                        break;
                case '?':
                        /*
@@ -1842,6 +1893,8 @@ int main(int argc, char *argv[])
                }
        } else if (!strcmp(cmd, "del"))
                ret = cmd_dev_del(&ctx);
+       else if (!strcmp(cmd, "stop"))
+               ret = cmd_dev_stop(&ctx);
        else if (!strcmp(cmd, "list")) {
                ctx.all = 1;
                ret = cmd_dev_list(&ctx);
index 96c66b337bc01dc1dfc11e15752bc5906c0b170c..cb757fd9bf9d8ce5a67f2e01a9487cb824388a3d 100644 (file)
@@ -83,6 +83,7 @@ struct dev_ctx {
        __u8 pi_offset;
        __u8 csum_type;
        __u8 tag_size;
+       unsigned int    safe_stop:1;
 
        int _evtfd;
        int _shmid;
diff --git a/tools/testing/selftests/ublk/test_generic_16.sh b/tools/testing/selftests/ublk/test_generic_16.sh
new file mode 100755 (executable)
index 0000000..e08af7b
--- /dev/null
@@ -0,0 +1,57 @@
+#!/bin/bash
+# SPDX-License-Identifier: GPL-2.0
+
+. "$(cd "$(dirname "$0")" && pwd)"/test_common.sh
+
+TID="generic_16"
+ERR_CODE=0
+
+_prep_test "null" "stop --safe command"
+
+# Check if SAFE_STOP_DEV feature is supported
+if ! _have_feature "SAFE_STOP_DEV"; then
+       _cleanup_test "null"
+       exit "$UBLK_SKIP_CODE"
+fi
+
+# Test 1: stop --safe on idle device should succeed
+dev_id=$(_add_ublk_dev -t null -q 2 -d 32)
+_check_add_dev $TID $?
+
+# Device is idle (no openers), stop --safe should succeed
+if ! ${UBLK_PROG} stop -n "${dev_id}" --safe; then
+       echo "stop --safe on idle device failed unexpectedly!"
+       ERR_CODE=255
+fi
+
+# Clean up device
+${UBLK_PROG} del -n "${dev_id}" > /dev/null 2>&1
+udevadm settle
+
+# Test 2: stop --safe on device with active opener should fail
+dev_id=$(_add_ublk_dev -t null -q 2 -d 32)
+_check_add_dev $TID $?
+
+# Open device in background (dd reads indefinitely)
+dd if=/dev/ublkb${dev_id} of=/dev/null bs=4k iflag=direct > /dev/null 2>&1 &
+dd_pid=$!
+
+# Give dd time to start
+sleep 0.2
+
+# Device has active opener, stop --safe should fail with -EBUSY
+if ${UBLK_PROG} stop -n "${dev_id}" --safe 2>/dev/null; then
+       echo "stop --safe on busy device succeeded unexpectedly!"
+       ERR_CODE=255
+fi
+
+# Kill dd and clean up
+kill $dd_pid 2>/dev/null
+wait $dd_pid 2>/dev/null
+
+# Now device should be idle, regular delete should work
+${UBLK_PROG} del -n "${dev_id}"
+udevadm settle
+
+_cleanup_test "null"
+_show_result $TID $ERR_CODE