dev_disk: add pr_dump_dir handler attribute to dump PR state on detach

When pr_dump_dir is set to a directory path, each dev_disk device writes
its PR state to <dir>/<serial> on detach, using the same text format as
the pr_state sysfs attribute.  The default is an empty string, which
disables the feature entirely.

This provides a race-free way to capture PR state at the point of device
teardown, after all in-flight commands have completed, for use cases that
need to preserve PR state across a device transition.

The dump must happen before scst_pr_clear_dev() wipes the in-memory
registrant list during device unregistration.  To achieve this, a new
optional pre_unregister() callback is added to struct scst_dev_type,
called from both scst_unregister_device() and
scst_unregister_virtual_device() before scst_pr_clear_dev().  The disk
handler registers this callback (disk_pre_unregister) to perform the
dump at the correct moment.

The work is split across two phases to avoid filesystem I/O while
scst_mutex is held.  disk_pre_unregister() (called under scst_mutex)
captures the PR state into a heap buffer and records the destination
path.  disk_detach() (called after scst_mutex is released) writes the
buffer to the filesystem.  To carry state between the two phases, dh_priv
is changed from a bare serial-number string to a struct disk_dh_priv
containing the serial plus the captured dump fields.
This commit is contained in:
Brian M
2026-03-06 10:27:38 -08:00
committed by Gleb Chesnokov
parent b92e091999
commit 3777c775c7
4 changed files with 210 additions and 19 deletions

View File

@@ -1497,6 +1497,16 @@ struct scst_dev_type {
*/
int (*attach)(struct scst_device *dev);
/*
* Called just before a device is unregistered, while the PR state is
* still intact. This fires before the core clears in-memory PR state
* (scst_pr_clear_dev) and before detach() is called, so it is the
* correct place to snapshot or dump PR state.
*
* OPTIONAL
*/
void (*pre_unregister)(struct scst_device *dev);
/*
* Called when a device is detaching from the dev handler.
*
@@ -5689,6 +5699,7 @@ int scst_remove_file(const char *name);
void scst_set_tp_soft_threshold_reached_UA(struct scst_tgt_dev *tgt_dev);
int scst_pr_set_cluster_mode(struct scst_device *dev, bool cluster_mode, const char *cl_dev_id);
void scst_vfs_unlink_and_put(struct path *path);
int scst_pr_init_dev(struct scst_device *dev);
void scst_pr_clear_dev(struct scst_device *dev);

View File

@@ -25,6 +25,9 @@
#include <linux/init.h>
#include <linux/blkdev.h>
#include <scsi/scsi_host.h>
#include <linux/fs.h>
#include <linux/limits.h>
#include <linux/namei.h>
#include <linux/slab.h>
#define LOG_PREFIX "dev_disk"
@@ -35,12 +38,20 @@
#include "scst.h"
#endif
#include "scst_dev_handler.h"
#include "../scst_pres.h"
# define DISK_NAME "dev_disk"
# define DISK_PERF_NAME "dev_disk_perf"
#define DISK_DEF_BLOCK_SHIFT 9
struct disk_dh_priv {
char *serial; /* SCSI Unit Serial Number (VPD page 0x80) */
char *pr_dump_path; /* <pr_dump_dir>/<serial>, set by pre_unregister */
char *pr_dump_buf; /* PR state text, set by pre_unregister */
ssize_t pr_dump_len;
};
static int disk_attach(struct scst_device *dev)
{
int res, rc;
@@ -150,17 +161,26 @@ static int disk_attach(struct scst_device *dev)
int32_t serial_length = get_unaligned_be16(&buffer[2]);
/*
* Since we only need the serial number we'll
* store directly in dh_priv. Failure is OK.
* Store serial in a disk_dh_priv struct on dh_priv.
* Failure is OK.
*/
dev->dh_priv = kzalloc(serial_length + 1, GFP_KERNEL);
if (!dev->dh_priv) {
PRINT_ERROR("Buffer memory allocation (size %d) failure",
serial_length + 1);
struct disk_dh_priv *priv = kzalloc(sizeof(*priv), GFP_KERNEL);
if (!priv) {
PRINT_ERROR("Memory allocation failure for device %s",
dev->virt_name);
} else {
memcpy(dev->dh_priv, &buffer[4], serial_length);
PRINT_INFO("%s: Obtained serial number: %s",
dev->virt_name, (char *)dev->dh_priv);
priv->serial = kzalloc(serial_length + 1, GFP_KERNEL);
if (!priv->serial) {
PRINT_ERROR("Memory allocation (size %d) failure for device %s",
serial_length + 1, dev->virt_name);
kfree(priv);
} else {
memcpy(priv->serial, &buffer[4], serial_length);
PRINT_INFO("%s: Obtained serial number: %s",
dev->virt_name, priv->serial);
dev->dh_priv = priv;
}
}
} else {
PRINT_WARNING("Failed to obtain serial number for device %s",
@@ -172,9 +192,11 @@ no_serial:
if (res != 0) {
PRINT_ERROR("Failed to obtain control parameters for device %s",
dev->virt_name);
kfree(dev->dh_priv);
dev->dh_priv = NULL;
if (dev->dh_priv) {
kfree(((struct disk_dh_priv *)dev->dh_priv)->serial);
kfree(dev->dh_priv);
dev->dh_priv = NULL;
}
goto out_free_buf;
}
@@ -186,12 +208,118 @@ out:
return res;
}
/*
* Directory into which PR state is dumped on device detach, named by the
* device serial number. Empty string (the default) disables the feature.
*/
static char disk_pr_dump_dir[PATH_MAX];
static DEFINE_MUTEX(disk_pr_dump_dir_mutex);
/*
* Called from disk_pre_unregister() while scst_mutex is held. Captures the
* PR state into a heap buffer and builds the destination path, storing both
* on the device's dh_priv so that disk_detach() can write the file after
* scst_mutex has been released. No filesystem I/O is performed here.
*/
static void disk_capture_pr_state(struct scst_device *dev)
{
struct disk_dh_priv *priv = dev->dh_priv;
char *path, *buf;
ssize_t len;
mutex_lock(&disk_pr_dump_dir_mutex);
if (!disk_pr_dump_dir[0]) {
mutex_unlock(&disk_pr_dump_dir_mutex);
return;
}
path = kasprintf(GFP_KERNEL, "%s/%s", disk_pr_dump_dir, priv->serial);
mutex_unlock(&disk_pr_dump_dir_mutex);
if (!path) {
PRINT_ERROR("%s: failed to allocate path for PR dump",
dev->virt_name);
return;
}
buf = kmalloc(PAGE_SIZE, GFP_KERNEL);
if (!buf) {
PRINT_ERROR("%s: failed to allocate buffer for PR dump",
dev->virt_name);
kfree(path);
return;
}
mutex_lock(&dev->dev_pr_mutex);
len = scst_pr_state_show(dev, buf, PAGE_SIZE);
mutex_unlock(&dev->dev_pr_mutex);
priv->pr_dump_path = path;
priv->pr_dump_buf = buf;
priv->pr_dump_len = len;
}
/*
* Called from disk_detach() after scst_mutex has been released. Writes the
* buffer captured by disk_capture_pr_state() to the filesystem.
*/
static void disk_write_pr_dump(struct scst_device *dev)
{
struct disk_dh_priv *priv = dev->dh_priv;
struct file *f;
ssize_t written;
loff_t pos = 0;
f = filp_open(priv->pr_dump_path, O_WRONLY | O_CREAT | O_TRUNC, 0600);
if (IS_ERR(f)) {
PRINT_ERROR("%s: failed to open PR dump file %s: %ld",
dev->virt_name, priv->pr_dump_path, PTR_ERR(f));
return;
}
written = kernel_write(f, priv->pr_dump_buf, priv->pr_dump_len, &pos);
filp_close(f, NULL);
if (written != priv->pr_dump_len) {
struct path fpath;
int rc;
PRINT_ERROR("%s: short write to PR dump file %s: %zd/%zd",
dev->virt_name, priv->pr_dump_path, written,
priv->pr_dump_len);
rc = kern_path(priv->pr_dump_path, 0, &fpath);
if (!rc)
scst_vfs_unlink_and_put(&fpath);
else
TRACE_PR("Unable to lookup '%s' - error %d",
priv->pr_dump_path, rc);
} else {
TRACE_DBG("%s: dumped PR state (%zd bytes) to %s",
dev->virt_name, priv->pr_dump_len, priv->pr_dump_path);
}
}
static void disk_pre_unregister(struct scst_device *dev)
{
if (dev->dh_priv)
disk_capture_pr_state(dev);
}
static void disk_detach(struct scst_device *dev)
{
if (dev->dh_priv) {
scst_pr_set_cluster_mode(dev, false, dev->dh_priv);
kfree(dev->dh_priv);
}
struct disk_dh_priv *priv = dev->dh_priv;
if (!priv)
return;
if (priv->pr_dump_buf)
disk_write_pr_dump(dev);
scst_pr_set_cluster_mode(dev, false, priv->serial);
kfree(priv->pr_dump_buf);
kfree(priv->pr_dump_path);
kfree(priv->serial);
kfree(priv);
}
static int disk_parse(struct scst_cmd *cmd)
@@ -599,8 +727,8 @@ static int disk_sysfs_process_cluster_mode_store(struct scst_sysfs_work_item *wo
if (clm < 0 || clm > 1)
goto unlock;
if (clm != dev->cluster_mode) {
/* dev->dh_priv contains the serial number string */
res = scst_pr_set_cluster_mode(dev, clm, dev->dh_priv);
res = scst_pr_set_cluster_mode(dev, clm,
((struct disk_dh_priv *)dev->dh_priv)->serial);
if (res)
goto unlock;
dev->cluster_mode = clm;
@@ -632,7 +760,7 @@ static ssize_t disk_sysfs_cluster_mode_store(struct kobject *kobj, struct kobj_a
TRACE_ENTRY();
/* Must have a serial number to enable cluster_mode */
if (count && !dev->dh_priv) {
if (count && !(dev->dh_priv && ((struct disk_dh_priv *)dev->dh_priv)->serial)) {
res = -ENOENT;
goto out;
}
@@ -673,6 +801,48 @@ static const struct attribute *disk_attrs[] = {
NULL,
};
static ssize_t disk_devtype_pr_dump_dir_show(struct kobject *kobj,
struct kobj_attribute *attr,
char *buf)
{
ssize_t ret;
mutex_lock(&disk_pr_dump_dir_mutex);
ret = sysfs_emit(buf, "%s\n", disk_pr_dump_dir);
mutex_unlock(&disk_pr_dump_dir_mutex);
return ret;
}
static ssize_t disk_devtype_pr_dump_dir_store(struct kobject *kobj,
struct kobj_attribute *attr,
const char *buf, size_t count)
{
size_t len = count;
if (len > 0 && buf[len - 1] == '\n')
len--;
if (len >= PATH_MAX)
return -ENAMETOOLONG;
mutex_lock(&disk_pr_dump_dir_mutex);
memcpy(disk_pr_dump_dir, buf, len);
disk_pr_dump_dir[len] = '\0';
mutex_unlock(&disk_pr_dump_dir_mutex);
return count;
}
static struct kobj_attribute disk_pr_dump_dir_attr =
__ATTR(pr_dump_dir, 0644, disk_devtype_pr_dump_dir_show,
disk_devtype_pr_dump_dir_store);
static const struct attribute *disk_devtype_attrs[] = {
&disk_pr_dump_dir_attr.attr,
NULL,
};
static struct scst_dev_type disk_devtype = {
.name = DISK_NAME,
.type = TYPE_DISK,
@@ -680,12 +850,14 @@ static struct scst_dev_type disk_devtype = {
.parse_atomic = 1,
.dev_done_atomic = 1,
.attach = disk_attach,
.pre_unregister = disk_pre_unregister,
.detach = disk_detach,
.parse = disk_parse,
.exec = disk_exec,
.on_sg_tablesize_low = disk_on_sg_tablesize_low,
.dev_done = disk_done,
.dev_attrs = disk_attrs,
.devt_attrs = disk_devtype_attrs,
#if defined(CONFIG_SCST_DEBUG) || defined(CONFIG_SCST_TRACING)
.default_trace_flags = SCST_DEFAULT_DEV_LOG_FLAGS,
.trace_flags = &trace_flag,
@@ -698,6 +870,7 @@ static struct scst_dev_type disk_devtype_perf = {
.parse_atomic = 1,
.dev_done_atomic = 1,
.attach = disk_attach,
.pre_unregister = disk_pre_unregister,
.detach = disk_detach,
.parse = disk_parse,
.exec = disk_perf_exec,

View File

@@ -15408,6 +15408,7 @@ void scst_vfs_unlink_and_put(struct path *path)
#endif
path_put(path);
}
EXPORT_SYMBOL(scst_vfs_unlink_and_put);
int scst_copy_file(const char *src, const char *dest)
{

View File

@@ -1122,6 +1122,9 @@ static void scst_unregister_device(struct scsi_device *scsidp)
list_del_init(&dev->dev_list_entry);
if (dev->handler->pre_unregister)
dev->handler->pre_unregister(dev);
/* For forwarding sources (see also tgt_forward_src). */
scst_pr_clear_dev(dev);
@@ -1405,6 +1408,9 @@ void scst_unregister_virtual_device(int id,
list_del_init(&dev->dev_list_entry);
if (dev->handler->pre_unregister)
dev->handler->pre_unregister(dev);
scst_pr_clear_dev(dev);
scst_dg_dev_remove_by_dev(dev);