mirror of
https://github.com/SCST-project/scst.git
synced 2026-05-22 21:21:28 +00:00
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:
@@ -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);
|
||||
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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)
|
||||
{
|
||||
|
||||
@@ -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);
|
||||
|
||||
Reference in New Issue
Block a user