diff --git a/scst/README b/scst/README index ba4c4f5d5..1f42e569e 100644 --- a/scst/README +++ b/scst/README @@ -1043,6 +1043,12 @@ Each vdisk_fileio's device has the following attributes in - size_mb - contains size of this virtual device in MB. + - pr_file_name - Full path of the file or block device in which to store + persistent reservation information. The default value for this attribute is + /var/lib/scst/pr/${device_name}. Writing a new value into this sysfs + attribute is only allowed if the device is not exported. Modifying this + sysfs attribute causes the persistent reservation state to be reloaded. + - t10_dev_id - contains and allows to set T10 vendor specific identifier for Device Identification VPD page (0x83) of INQUIRY data. By default VDISK handler always generates t10_dev_id for every new diff --git a/scst/include/scst.h b/scst/include/scst.h index 53142c430..cfe71067a 100644 --- a/scst/include/scst.h +++ b/scst/include/scst.h @@ -2533,6 +2533,9 @@ struct scst_device { /* True if persist through power loss is activated. */ unsigned short pr_aptpl:1; + /* Whether or not pr_file_name has been modified via sysfs. */ + unsigned int pr_file_name_is_set:1; + /* Persistent reservation type */ uint8_t pr_type; @@ -2562,7 +2565,10 @@ struct scst_device { struct scst_order_data dev_order_data; - /* Persist through power loss files */ + /* + * Where to save persistent reservation information. Protected by + * dev_pr_mutex. + */ char *pr_file_name; char *pr_file_name1; @@ -4700,7 +4706,10 @@ struct scst_sysfs_work_item { }; struct { struct scst_device *dev; - int new_threads_num; + union { + int new_threads_num; + bool default_val; + }; enum scst_dev_type_threads_pool_type new_threads_pool_type; }; struct scst_session *sess; @@ -4743,6 +4752,9 @@ int scst_scsi_exec_async(struct scst_cmd *cmd, void *data, void (*done)(void *data, char *sense, int result, int resid)); #endif +int scst_get_file_mode(const char *path); +bool scst_parent_dir_exists(const char *path); + struct scst_data_descriptor { uint64_t sdd_lba; uint64_t sdd_blocks; diff --git a/scst/src/scst_lib.c b/scst/src/scst_lib.c index cd7f98eac..874594aa8 100644 --- a/scst/src/scst_lib.c +++ b/scst/src/scst_lib.c @@ -3759,6 +3759,17 @@ void scst_free_device(struct scst_device *dev) return; } +bool scst_device_is_exported(struct scst_device *dev) +{ +#if LINUX_VERSION_CODE >= KERNEL_VERSION(2, 6, 32) + lockdep_assert_held(&scst_mutex); +#endif + + WARN_ON_ONCE(!dev->dev_tgt_dev_list.next); + + return !list_empty(&dev->dev_tgt_dev_list); +} + /** * scst_init_mem_lim - initialize memory limits structure * @@ -10143,6 +10154,55 @@ int scst_read_file_transactional(const char *name, const char *name1, } EXPORT_SYMBOL_GPL(scst_read_file_transactional); +/* + * Return the file mode if @path exists or an error code if opening @path via + * filp_open() in read-only mode failed. + */ +int scst_get_file_mode(const char *path) +{ + struct file *file; + int res; + + file = filp_open(path, O_RDONLY, 0400); + if (IS_ERR(file)) { + res = PTR_ERR(file); + goto out; + } + res = file->f_dentry->d_inode->i_mode; + filp_close(file, NULL); + +out: + return res; +} +EXPORT_SYMBOL(scst_get_file_mode); + +/* + * Return true if either @path does not contain a slash or if the directory + * specified in @path exists. + */ +bool scst_parent_dir_exists(const char *path) +{ + const char *last_slash = strrchr(path, '/'); + const char *dir; + int dir_mode; + bool res = true; + + if (last_slash && last_slash > path) { + dir = kasprintf(GFP_KERNEL, "%.*s", (int)(last_slash - path), + path); + if (dir) { + dir_mode = scst_get_file_mode(dir); + kfree(dir); + res = dir_mode >= 0 && S_ISDIR(dir_mode); + } else { + res = false; + } + } + + return res; +} +EXPORT_SYMBOL(scst_parent_dir_exists); + static void __init scst_scsi_op_list_init(void) { int i; diff --git a/scst/src/scst_main.c b/scst/src/scst_main.c index d3f24e383..9fb82157b 100644 --- a/scst/src/scst_main.c +++ b/scst/src/scst_main.c @@ -1198,13 +1198,13 @@ out: #ifndef CONFIG_SCST_PROC out_del_unlocked: mutex_lock(&scst_mutex); - list_del(&dev->dev_list_entry); + list_del_init(&dev->dev_list_entry); mutex_unlock(&scst_mutex); scst_free_device(dev); goto out; #else out_del_locked: - list_del(&dev->dev_list_entry); + list_del_init(&dev->dev_list_entry); #endif out_free_dev: @@ -1266,7 +1266,7 @@ static void scst_unregister_device(struct scsi_device *scsidp) dev->dev_unregistering = 1; - list_del(&dev->dev_list_entry); + list_del_init(&dev->dev_list_entry); scst_dg_dev_remove_by_dev(dev); @@ -1418,6 +1418,11 @@ int scst_register_virtual_device(struct scst_dev_type *dev_handler, scst_virt_dev_last_id = 1; } + res = scst_pr_set_file_name(dev, NULL, "%s/%s", SCST_PR_DIR, + dev->virt_name); + if (res != 0) + goto out_free_dev; + res = scst_pr_init_dev(dev); if (res != 0) goto out_free_dev; @@ -1516,7 +1521,7 @@ void scst_unregister_virtual_device(int id) dev->dev_unregistering = 1; - list_del(&dev->dev_list_entry); + list_del_init(&dev->dev_list_entry); scst_pr_clear_dev(dev); diff --git a/scst/src/scst_pres.c b/scst/src/scst_pres.c index 61017a4a8..e5ce9fa1b 100644 --- a/scst/src/scst_pres.c +++ b/scst/src/scst_pres.c @@ -45,6 +45,7 @@ #endif #include #include +#include #if LINUX_VERSION_CODE < KERNEL_VERSION(2,6,25) #include @@ -74,6 +75,19 @@ #define isblank(c) ((c) == ' ' || (c) == '\t') #endif +#if LINUX_VERSION_CODE >= KERNEL_VERSION(2, 6, 32) && defined(CONFIG_LOCKDEP) +#define scst_assert_pr_mutex_held(dev) \ + do { \ + if (dev->dev_list_entry.next && \ + !list_empty(&dev->dev_list_entry)) \ + lockdep_assert_held(&dev->dev_pr_mutex); \ + } while (0); +#else +static inline void scst_assert_pr_mutex_held(struct scst_device *dev) +{ +} +#endif + static inline int tid_size(const uint8_t *tid) { sBUG_ON(tid == NULL); @@ -174,6 +188,8 @@ out_error: static inline void scst_pr_set_holder(struct scst_device *dev, struct scst_dev_registrant *holder, uint8_t scope, uint8_t type) { + scst_assert_pr_mutex_held(dev); + dev->pr_is_set = 1; dev->pr_scope = scope; dev->pr_type = type; @@ -190,6 +206,8 @@ static bool scst_pr_is_holder(struct scst_device *dev, TRACE_ENTRY(); + scst_assert_pr_mutex_held(dev); + if (!dev->pr_is_set) goto out; @@ -209,6 +227,8 @@ out: /* Must be called under dev_pr_mutex */ void scst_pr_dump_prs(struct scst_device *dev, bool force) { + scst_assert_pr_mutex_held(dev); + if (!force) { #if defined(CONFIG_SCST_DEBUG) if ((trace_flag & TRACE_PRES) == 0) @@ -265,6 +285,8 @@ static void scst_pr_find_registrants_list_all(struct scst_device *dev, TRACE_ENTRY(); + scst_assert_pr_mutex_held(dev); + TRACE_PR("Finding all registered records for device '%s' " "with exclude reg key %016llx", dev->virt_name, be64_to_cpu(exclude_reg->key)); @@ -291,6 +313,8 @@ static void scst_pr_find_registrants_list_key(struct scst_device *dev, TRACE_ENTRY(); + scst_assert_pr_mutex_held(dev); + TRACE_PR("Finding registrants for device '%s' with key %016llx", dev->virt_name, be64_to_cpu(key)); @@ -320,6 +344,8 @@ static struct scst_dev_registrant *scst_pr_find_reg( TRACE_ENTRY(); + scst_assert_pr_mutex_held(dev); + list_for_each_entry(reg, &dev->dev_registrants_list, dev_registrants_list_entry) { if ((reg->rel_tgt_id == rel_tgt_id) && @@ -338,6 +364,8 @@ static void scst_pr_clear_reservation(struct scst_device *dev) { TRACE_ENTRY(); + scst_assert_pr_mutex_held(dev); + WARN_ON(!dev->pr_is_set); dev->pr_is_set = 0; @@ -355,6 +383,8 @@ static void scst_pr_clear_holder(struct scst_device *dev) { TRACE_ENTRY(); + scst_assert_pr_mutex_held(dev); + WARN_ON(!dev->pr_is_set); if (dev->pr_type == TYPE_WRITE_EXCLUSIVE_ALL_REG || @@ -382,6 +412,8 @@ static struct scst_dev_registrant *scst_pr_add_registrant( TRACE_ENTRY(); + scst_assert_pr_mutex_held(dev); + sBUG_ON(dev == NULL); sBUG_ON(transport_id == NULL); @@ -465,6 +497,8 @@ static void scst_pr_remove_registrant(struct scst_device *dev, { TRACE_ENTRY(); + scst_assert_pr_mutex_held(dev); + TRACE_PR("Removing registrant %s/%d (reg %p, tgt_dev %p, key %016llx, " "dev %s)", debug_transport_id_to_initiator_name(reg->transport_id), reg->rel_tgt_id, reg, reg->tgt_dev, be64_to_cpu(reg->key), @@ -485,6 +519,18 @@ static void scst_pr_remove_registrant(struct scst_device *dev, return; } +static void scst_pr_remove_registrants(struct scst_device *dev) +{ + struct scst_dev_registrant *reg, *tmp_reg; + + scst_assert_pr_mutex_held(dev); + + list_for_each_entry_safe(reg, tmp_reg, &dev->dev_registrants_list, + dev_registrants_list_entry) { + scst_pr_remove_registrant(dev, reg); + } +} + /* Must be called under dev_pr_mutex */ static void scst_pr_send_ua_reg(struct scst_device *dev, struct scst_dev_registrant *reg, @@ -494,6 +540,8 @@ static void scst_pr_send_ua_reg(struct scst_device *dev, TRACE_ENTRY(); + scst_assert_pr_mutex_held(dev); + scst_set_sense(ua, sizeof(ua), dev->d_sense, key, asc, ascq); TRACE_PR("Queueing UA [%x %x %x]: registrant %s/%d (%p), tgt_dev %p, " @@ -517,6 +565,8 @@ static void scst_pr_send_ua_all(struct scst_device *dev, TRACE_ENTRY(); + scst_assert_pr_mutex_held(dev); + list_for_each_entry(reg, &dev->dev_registrants_list, dev_registrants_list_entry) { if (reg != exclude_reg) @@ -537,6 +587,8 @@ static void scst_pr_abort_reg(struct scst_device *dev, TRACE_ENTRY(); + scst_assert_pr_mutex_held(dev); + if (reg->tgt_dev == NULL) { TRACE_PR("Registrant %s/%d (%p, key 0x%016llx) has no session", debug_transport_id_to_initiator_name(reg->transport_id), @@ -612,6 +664,10 @@ static int scst_pr_do_load_device_file(struct scst_device *dev, TRACE_ENTRY(); + scst_assert_pr_mutex_held(dev); + + scst_pr_remove_registrants(dev); + old_fs = get_fs(); set_fs(KERNEL_DS); @@ -767,10 +823,12 @@ out: static int scst_pr_load_device_file(struct scst_device *dev) { - int res; + int res, rc; TRACE_ENTRY(); + scst_assert_pr_mutex_held(dev); + if (dev->pr_file_name == NULL || dev->pr_file_name1 == NULL) { PRINT_ERROR("Invalid file paths for '%s'", dev->virt_name); res = -EINVAL; @@ -779,12 +837,20 @@ static int scst_pr_load_device_file(struct scst_device *dev) res = scst_pr_do_load_device_file(dev, dev->pr_file_name); if (res == 0) - goto out; + goto out_dump; else if (res == -ENOMEM) goto out; - res = scst_pr_do_load_device_file(dev, dev->pr_file_name1); + rc = res; + res = scst_pr_do_load_device_file(dev, dev->pr_file_name1); + if (res != 0) { + if (res == -ENOENT) + res = rc; + goto out; + } + +out_dump: scst_pr_dump_prs(dev, false); out: @@ -799,6 +865,8 @@ static void scst_pr_remove_device_files(struct scst_tgt_dev *tgt_dev) TRACE_ENTRY(); + scst_assert_pr_mutex_held(dev); + res = dev->pr_file_name ? scst_remove_file(dev->pr_file_name) : -ENOENT; res = dev->pr_file_name1 ? scst_remove_file(dev->pr_file_name1) : -ENOENT; @@ -821,6 +889,8 @@ void scst_pr_sync_device_file(struct scst_tgt_dev *tgt_dev, struct scst_cmd *cmd TRACE_ENTRY(); + scst_assert_pr_mutex_held(dev); + if ((dev->pr_aptpl == 0) || list_empty(&dev->dev_registrants_list)) { scst_pr_remove_device_files(tgt_dev); goto out; @@ -1003,107 +1073,107 @@ write_error_close: goto out_set_fs; } -static int scst_pr_check_pr_path(void) +#endif /* CONFIG_SCST_PROC */ + +/** + * scst_pr_set_file_name - set name of file in which to save PR information + * @dev: SCST device. + * @prev: If not NULL, the current path will be stored in *@prev. It is the + * responsibility of the caller to invoke kfree(*@prev) at an + * appropriate time. + * @fmt: Full path of the file in which to save PR info. + * + * This function must be called either while @dev is not on the device list + * or with scst_mutex held. + */ +int scst_pr_set_file_name(struct scst_device *dev, char **prev, + const char *fmt, ...) { - int res; -#if LINUX_VERSION_CODE < KERNEL_VERSION(2, 6, 39) - struct nameidata nd; -#else - struct path path; -#endif + va_list args; + char *pr_file_name = NULL, *bkp = NULL; + int file_mode, res = -EINVAL; - mm_segment_t old_fs = get_fs(); + scst_assert_pr_mutex_held(dev); - TRACE_ENTRY(); + sBUG_ON(!fmt); - set_fs(KERNEL_DS); - -#if LINUX_VERSION_CODE < KERNEL_VERSION(2, 6, 39) - res = path_lookup(SCST_PR_DIR, 0, &nd); - if (res == 0) - scst_path_put(&nd); -#else - res = kern_path(SCST_PR_DIR, 0, &path); - if (res == 0) - path_put(&path); -#endif - if (res != 0) { - PRINT_ERROR("Unable to find %s (err %d), you should create " - "this directory manually or reinstall SCST", - SCST_PR_DIR, res); - goto out_setfs; + res = -ENOMEM; + va_start(args, fmt); + pr_file_name = kvasprintf(GFP_KERNEL, fmt, args); + va_end(args); + if (!pr_file_name) { + PRINT_ERROR("Unable to kvasprintf() new PR file name"); + goto out; } -out_setfs: - set_fs(old_fs); + res = -EINVAL; + if (pr_file_name[0] != '/') { + PRINT_ERROR("PR file name must be absolute!"); + goto out; + } - TRACE_EXIT_RES(res); + file_mode = scst_get_file_mode(pr_file_name); + if (file_mode >= 0 && !S_ISREG(file_mode) && !S_ISBLK(file_mode)) { + PRINT_ERROR("PR file name must be file or block device!"); + goto out; + } + + res = -ENOENT; + if (!scst_parent_dir_exists(pr_file_name)) { + PRINT_ERROR("PR file name parent directory doesn't exist"); + goto out; + } + + res = -ENOMEM; + bkp = kasprintf(GFP_KERNEL, "%s.1", pr_file_name); + if (!bkp) { + PRINT_ERROR("Unable to kasprintf() backup PR file name"); + goto out; + } + if (prev) { + *prev = dev->pr_file_name; + dev->pr_file_name = pr_file_name; + pr_file_name = NULL; + } else + swap(dev->pr_file_name, pr_file_name); + swap(dev->pr_file_name1, bkp); + res = 0; + +out: + kfree(pr_file_name); + kfree(bkp); return res; } -#endif /* CONFIG_SCST_PROC */ - -/* Called under scst_mutex */ +/* Must be called under dev_pr_mutex or before dev is on the device list. */ int scst_pr_init_dev(struct scst_device *dev) { int res = 0; TRACE_ENTRY(); - dev->pr_file_name = kasprintf(GFP_KERNEL, "%s/%s", SCST_PR_DIR, - dev->virt_name); - if (dev->pr_file_name == NULL) { - PRINT_ERROR("Allocation of device '%s' file path failed", - dev->virt_name); - res = -ENOMEM; - goto out; - } - dev->pr_file_name1 = kasprintf(GFP_KERNEL, "%s/%s.1", SCST_PR_DIR, - dev->virt_name); - if (dev->pr_file_name1 == NULL) { - PRINT_ERROR("Allocation of device '%s' backup file path failed", - dev->virt_name); - res = -ENOMEM; - goto out_free_name; - } + scst_assert_pr_mutex_held(dev); + + sBUG_ON(!dev->pr_file_name || !dev->pr_file_name1); #ifndef CONFIG_SCST_PROC - res = scst_pr_check_pr_path(); - if (res == 0) { - res = scst_pr_load_device_file(dev); - if (res == -ENOENT) - res = 0; - } + res = scst_pr_load_device_file(dev); + if (res == -ENOENT) + res = 0; #endif - if (res != 0) - goto out_free_name1; - -out: TRACE_EXIT_RES(res); return res; - -out_free_name1: - kfree(dev->pr_file_name1); - dev->pr_file_name1 = NULL; - -out_free_name: - kfree(dev->pr_file_name); - dev->pr_file_name = NULL; - goto out; } /* Called under scst_mutex */ void scst_pr_clear_dev(struct scst_device *dev) { - struct scst_dev_registrant *reg, *tmp_reg; - TRACE_ENTRY(); - list_for_each_entry_safe(reg, tmp_reg, &dev->dev_registrants_list, - dev_registrants_list_entry) { - scst_pr_remove_registrant(dev, reg); - } + scst_assert_pr_mutex_held(dev); + + scst_pr_remove_registrants(dev); kfree(dev->pr_file_name); kfree(dev->pr_file_name1); @@ -1198,6 +1268,8 @@ static int scst_pr_register_with_spec_i_pt(struct scst_cmd *cmd, struct scst_dev_registrant *reg; uint8_t *transport_id; + scst_assert_pr_mutex_held(cmd->dev); + action_key = get_unaligned((__be64 *)&buffer[8]); ext_size = get_unaligned_be32(&buffer[24]); @@ -1315,6 +1387,8 @@ static void scst_pr_unregister(struct scst_device *dev, TRACE_ENTRY(); + scst_assert_pr_mutex_held(dev); + TRACE_PR("Unregistering key %0llx", reg->key); is_holder = scst_pr_is_holder(dev, reg); @@ -1346,6 +1420,8 @@ static void scst_pr_unregister_all_tg_pt(struct scst_device *dev, TRACE_ENTRY(); + scst_assert_pr_mutex_held(dev); + /* * We can't use scst_mutex here since the caller already holds * dev_pr_mutex. @@ -1388,6 +1464,8 @@ static int scst_pr_register_on_tgt_id(struct scst_cmd *cmd, TRACE_ENTRY(); + scst_assert_pr_mutex_held(cmd->dev); + TRACE_PR("rel_tgt_id %d, spec_i_pt %d", rel_tgt_id, spec_i_pt); if (spec_i_pt) { @@ -1433,6 +1511,8 @@ static int scst_pr_register_all_tg_pt(struct scst_cmd *cmd, uint8_t *buffer, TRACE_ENTRY(); + scst_assert_pr_mutex_held(cmd->dev); + /* * We can't use scst_mutex here because the caller already holds * dev_pr_mutex. @@ -1478,6 +1558,8 @@ static int __scst_pr_register(struct scst_cmd *cmd, uint8_t *buffer, TRACE_ENTRY(); + scst_assert_pr_mutex_held(cmd->dev); + if (all_tg_pt) { res = scst_pr_register_all_tg_pt(cmd, buffer, buffer_size, spec_i_pt, &rollback_list); @@ -1524,6 +1606,8 @@ void scst_pr_register(struct scst_cmd *cmd, uint8_t *buffer, int buffer_size) TRACE_ENTRY(); + scst_assert_pr_mutex_held(cmd->dev); + aptpl = buffer[20] & 0x01; spec_i_pt = (buffer[20] >> 3) & 0x01; all_tg_pt = (buffer[20] >> 2) & 0x01; @@ -1618,6 +1702,8 @@ void scst_pr_register_and_ignore(struct scst_cmd *cmd, uint8_t *buffer, TRACE_ENTRY(); + scst_assert_pr_mutex_held(cmd->dev); + aptpl = buffer[20] & 0x01; all_tg_pt = (buffer[20] >> 2) & 0x01; action_key = get_unaligned((__be64 *)&buffer[8]); @@ -1696,6 +1782,8 @@ void scst_pr_register_and_move(struct scst_cmd *cmd, uint8_t *buffer, TRACE_ENTRY(); + scst_assert_pr_mutex_held(cmd->dev); + aptpl = buffer[17] & 0x01; key = get_unaligned((__be64 *)&buffer[0]); action_key = get_unaligned((__be64 *)&buffer[8]); @@ -1839,6 +1927,8 @@ void scst_pr_reserve(struct scst_cmd *cmd, uint8_t *buffer, int buffer_size) TRACE_ENTRY(); + scst_assert_pr_mutex_held(dev); + key = get_unaligned((__be64 *)&buffer[0]); scope = cmd->cdb[2] >> 4; type = cmd->cdb[2] & 0x0f; @@ -1926,6 +2016,8 @@ void scst_pr_release(struct scst_cmd *cmd, uint8_t *buffer, int buffer_size) TRACE_ENTRY(); + scst_assert_pr_mutex_held(dev); + key = get_unaligned((__be64 *)&buffer[0]); scope = cmd->cdb[2] >> 4; type = cmd->cdb[2] & 0x0f; @@ -2001,6 +2093,8 @@ void scst_pr_clear(struct scst_cmd *cmd, uint8_t *buffer, int buffer_size) TRACE_ENTRY(); + scst_assert_pr_mutex_held(dev); + key = get_unaligned((__be64 *)&buffer[0]); if (buffer_size != 24) { @@ -2211,6 +2305,8 @@ void scst_pr_preempt(struct scst_cmd *cmd, uint8_t *buffer, int buffer_size) { TRACE_ENTRY(); + scst_assert_pr_mutex_held(cmd->dev); + scst_pr_do_preempt(cmd, buffer, buffer_size, false); TRACE_EXIT(); @@ -2248,6 +2344,8 @@ void scst_pr_preempt_and_abort(struct scst_cmd *cmd, uint8_t *buffer, { TRACE_ENTRY(); + scst_assert_pr_mutex_held(cmd->dev); + cmd->pr_abort_counter = kzalloc(sizeof(*cmd->pr_abort_counter), GFP_KERNEL); if (cmd->pr_abort_counter == NULL) { @@ -2424,6 +2522,8 @@ void scst_pr_read_keys(struct scst_cmd *cmd, uint8_t *buffer, int buffer_size) TRACE_ENTRY(); + scst_assert_pr_mutex_held(dev); + if (buffer_size < 8) { TRACE_PR("buffer_size too small: %d. expected >= 8 " "(buffer %p)", buffer_size, buffer); @@ -2475,6 +2575,8 @@ void scst_pr_read_reservation(struct scst_cmd *cmd, uint8_t *buffer, TRACE_ENTRY(); + scst_assert_pr_mutex_held(dev); + if (buffer_size < 8) { TRACE_PR("buffer_size too small: %d. expected >= 8 " "(buffer %p)", buffer_size, buffer); @@ -2538,6 +2640,8 @@ void scst_pr_report_caps(struct scst_cmd *cmd, uint8_t *buffer, int buffer_size) TRACE_ENTRY(); + scst_assert_pr_mutex_held(dev); + if (buffer_size < 8) { TRACE_PR("buffer_size too small: %d. expected >= 8 " "(buffer %p)", buffer_size, buffer); @@ -2577,6 +2681,8 @@ void scst_pr_read_full_status(struct scst_cmd *cmd, uint8_t *buffer, TRACE_ENTRY(); + scst_assert_pr_mutex_held(cmd->dev); + if (buffer_size < 8) goto skip; diff --git a/scst/src/scst_pres.h b/scst/src/scst_pres.h index acb0e7e49..98b8f6f33 100644 --- a/scst/src/scst_pres.h +++ b/scst/src/scst_pres.h @@ -89,6 +89,9 @@ static inline void scst_pr_write_unlock(struct scst_device *dev) mutex_unlock(&dev->dev_pr_mutex); } +int scst_pr_set_file_name(struct scst_device *dev, char **prev, + const char *fmt, ...) __printf(3, 4); + int scst_pr_init_dev(struct scst_device *dev); void scst_pr_clear_dev(struct scst_device *dev); diff --git a/scst/src/scst_priv.h b/scst/src/scst_priv.h index 7df673ee8..ac0eeee89 100644 --- a/scst/src/scst_priv.h +++ b/scst/src/scst_priv.h @@ -335,6 +335,7 @@ void scst_free_tgt(struct scst_tgt *tgt); int scst_alloc_device(gfp_t gfp_mask, struct scst_device **out_dev); void scst_free_device(struct scst_device *dev); +bool scst_device_is_exported(struct scst_device *dev); struct scst_acg *scst_alloc_add_acg(struct scst_tgt *tgt, const char *acg_name, bool tgt_acg); diff --git a/scst/src/scst_sysfs.c b/scst/src/scst_sysfs.c index 5187d104d..5a79679bf 100644 --- a/scst/src/scst_sysfs.c +++ b/scst/src/scst_sysfs.c @@ -2756,6 +2756,135 @@ static ssize_t scst_dev_sysfs_type_show(struct kobject *kobj, static struct kobj_attribute dev_type_attr = __ATTR(type, S_IRUGO, scst_dev_sysfs_type_show, NULL); +static ssize_t scst_dev_sysfs_pr_file_name_show(struct kobject *kobj, + struct kobj_attribute *attr, + char *buf) +{ + struct scst_device *dev; + int res; + + dev = container_of(kobj, struct scst_device, dev_kobj); + + res = mutex_lock_interruptible(&dev->dev_pr_mutex); + if (res != 0) + goto out; + /* pr_file_name is NULL for SCSI pass-through devices */ + WARN_ON_ONCE(!dev->pr_file_name); + res = scnprintf(buf, PAGE_SIZE, "%s\n%s", dev->pr_file_name ? : "", + dev->pr_file_name_is_set ? SCST_SYSFS_KEY_MARK "\n" : + ""); + mutex_unlock(&dev->dev_pr_mutex); + +out: + return res; +} + +static int +scst_dev_sysfs_pr_file_name_process_store(struct scst_sysfs_work_item *work) +{ + struct scst_device *dev = work->dev; + char *pr_file_name = work->buf, *prev = NULL; + int res; + + res = mutex_lock_interruptible(&scst_mutex); + if (res != 0) + goto out; + + res = -EBUSY; + if (scst_device_is_exported(dev)) { + PRINT_ERROR("%s: not changing pr_file_name because the device" + " has already been exported", dev->virt_name); + goto unlock_scst; + } + + res = mutex_lock_interruptible(&dev->dev_pr_mutex); + if (res) + goto unlock_scst; + + if (strcmp(dev->pr_file_name, pr_file_name) == 0) + goto unlock_dev_pr; + + res = scst_pr_set_file_name(dev, &prev, "%s", pr_file_name); + if (res != 0) + goto unlock_dev_pr; + + res = scst_pr_init_dev(dev); + if (res != 0) { + PRINT_ERROR("%s: loading PR from %s failed (%d) - restoring %s", + dev->virt_name, dev->pr_file_name, res, + prev ? : ""); + scst_pr_set_file_name(dev, NULL, "%s", prev); + scst_pr_init_dev(dev); + goto unlock_dev_pr; + } + + dev->pr_file_name_is_set = !work->default_val; + +unlock_dev_pr: + mutex_unlock(&dev->dev_pr_mutex); + +unlock_scst: + mutex_unlock(&scst_mutex); + +out: + kobject_put(&dev->dev_kobj); + kfree(prev); + + return res; +} + +static ssize_t scst_dev_sysfs_pr_file_name_store(struct kobject *kobj, + struct kobj_attribute *attr, const char *buf, size_t count) +{ + struct scst_sysfs_work_item *work; + struct scst_device *dev; + char *pr_file_name, *p; + int res = -ENOMEM; + bool def = false; + + dev = container_of(kobj, struct scst_device, dev_kobj); + + pr_file_name = kasprintf(GFP_KERNEL, "%.*s", (int)count, buf); + if (!pr_file_name) { + PRINT_ERROR("Unable to kasprintf() PR file name"); + goto out; + } + p = pr_file_name; + strsep(&p, "\n"); /* strip trailing whitespace */ + if (pr_file_name[0] == '\0') { + kfree(pr_file_name); + pr_file_name = kasprintf(GFP_KERNEL, "%s/%s", SCST_PR_DIR, + dev->virt_name); + if (!pr_file_name) { + PRINT_ERROR("Unable to kasprintf() PR file name"); + goto out; + } + def = true; + } + + res = scst_alloc_sysfs_work(scst_dev_sysfs_pr_file_name_process_store, + false, &work); + if (res != 0) + goto out; + kobject_get(&dev->dev_kobj); + work->dev = dev; + work->default_val = def; + swap(work->buf, pr_file_name); + + res = scst_sysfs_queue_wait_work(work); + if (res == 0) + res = count; + +out: + kfree(pr_file_name); + return res; +} + +static struct kobj_attribute dev_pr_file_name_attr = + __ATTR(pr_file_name, S_IWUSR|S_IRUGO, + scst_dev_sysfs_pr_file_name_show, + scst_dev_sysfs_pr_file_name_store); + #if defined(CONFIG_SCST_DEBUG) || defined(CONFIG_SCST_TRACING) static ssize_t scst_dev_sysfs_dump_prs(struct kobject *kobj, @@ -3205,6 +3334,15 @@ int scst_dev_sysfs_create(struct scst_device *dev) dev->virt_name); goto out_del; } + } else { + res = sysfs_create_file(&dev->dev_kobj, + &dev_pr_file_name_attr.attr); + if (res != 0) { + PRINT_ERROR("Can't create attr %s for dev %s", + dev_pr_file_name_attr.attr.name, + dev->virt_name); + goto out_del; + } } #if defined(CONFIG_SCST_DEBUG) || defined(CONFIG_SCST_TRACING)