diff --git a/scst/include/scst.h b/scst/include/scst.h index 137ede92d..a60511514 100644 --- a/scst/include/scst.h +++ b/scst/include/scst.h @@ -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); diff --git a/scst/src/dev_handlers/scst_disk.c b/scst/src/dev_handlers/scst_disk.c index 653f6d297..953a7e5f1 100644 --- a/scst/src/dev_handlers/scst_disk.c +++ b/scst/src/dev_handlers/scst_disk.c @@ -25,6 +25,9 @@ #include #include #include +#include +#include +#include #include #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; /* /, 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, diff --git a/scst/src/scst_lib.c b/scst/src/scst_lib.c index d9e9f6d57..8ac079a72 100644 --- a/scst/src/scst_lib.c +++ b/scst/src/scst_lib.c @@ -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) { diff --git a/scst/src/scst_main.c b/scst/src/scst_main.c index a10cb69b5..b82b9da22 100644 --- a/scst/src/scst_main.c +++ b/scst/src/scst_main.c @@ -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);