struct tcmur_handler {
const char *name; /* Human-friendly name */
const char *subtype; /* Name for cfgstring matching */
const char *cfg_desc; /* Description of this backstore's config string */
void *opaque; /* Handler private data. */
/*
* As much as possible, check that the cfgstring will result
* in a working device when given to us as dev->cfgstring in
* the ->open() call.
*
* This function is optional but gives configuration tools a
* chance to warn users in advance if the device they're
* trying to create is invalid.
*
* Returns true if string is valid. Only if false, set *reason
* to a string that says why. The string will be free()ed.
* Suggest using asprintf().
*/
bool (*check_config)(const char *cfgstring, char **reason);
int (*reconfig)(struct tcmu_device *dev, struct tcmulib_cfg_info *cfg);
/* Per-device added/removed callbacks */
int (*open)(struct tcmu_device *dev, bool reopen);
void (*close)(struct tcmu_device *dev);
/*
* If > 0, runner will execute up to nr_threads IO callouts from
* threads.
* if 0, runner will call IO callouts from the cmd proc thread or
* completion context for compound commands.
*/
int nr_threads;
/*
* Async handle_cmd only handlers return:
*
* - TCMU_STS_OK if the command has been executed successfully
* - TCMU_STS_NOT_HANDLED if opcode is not handled
* - TCMU_STS_ASYNC_HANDLED if opcode is handled asynchronously
* - Non TCMU_STS_OK code indicating failure
* - TCMU_STS_PASSTHROUGH_ERR For handlers that require low level
* SCSI processing and want to setup their own sense buffers.
*
* Handlers that set nr_threads > 0 and async handlers
* that implement handle_cmd and the IO callouts below return:
*
* - TCMU_STS_OK if the handler has queued the command.
* - TCMU_STS_NOT_HANDLED if the command is not supported.
* - TCMU_STS_NO_RESOURCE if the handler was not able to allocate
* resources for the command.
*
* If TCMU_STS_OK is returned from the callout the handler must call
* the tcmulib_cmd->done function with TCMU_STS return code.
*/
handle_cmd_fn_t handle_cmd;
/*
* Below callbacks are only executed by generic_handle_cmd.
* Returns:
* - TCMU_STS_OK if the handler has queued the command.
* - TCMU_STS_NO_RESOURCE if the handler was not able to allocate
* resources for the command.
*
* If TCMU_STS_OK is returned from the callout the handler must call
* the tcmulib_cmd->done function with TCMU_STS return code.
*/
rw_fn_t write;
rw_fn_t read;
flush_fn_t flush;
unmap_fn_t unmap;
/*
* If the lock is acquired and the tag is not TCMU_INVALID_LOCK_TAG,
* it must be associated with the lock and returned by get_lock_tag on
* local and remote nodes. When unlock is successful, the tag
* associated with the lock must be deleted.
*
* Returns a TCMU_STS indicating success/failure.
*/
int (*lock)(struct tcmu_device *dev, uint16_t tag);
int (*unlock)(struct tcmu_device *dev);
/*
* Return tag set in lock call in tag buffer and a TCMU_STS
* indicating success/failure.
*/
int (*get_lock_tag)(struct tcmu_device *dev, uint16_t *tag);
/*
* Must return TCMUR_DEV_LOCK state value.
*/
int (*get_lock_state)(struct tcmu_device *dev);
/*
* internal field, don't touch this
*
* indicates to tcmu-runner whether this is an internal handler loaded
* via dlopen or an external handler registered via dbus. In the
* latter case opaque will point to a struct dbus_info.
*/
bool _is_dbus_handler;
/*
* Update the logdir called by dynamic config thread.
*/
bool (*update_logdir)(void);
};
实现方式一 全权接管命令处理
以 file_optical.c 为例:
/*
* Return scsi status or TCMU_STS_NOT_HANDLED
*/
static int fbo_handle_cmd(struct tcmu_device *dev, struct tcmulib_cmd *cmd)
{
uint8_t *cdb = cmd->cdb;
struct iovec *iovec = cmd->iovec;
size_t iov_cnt = cmd->iov_cnt;
uint8_t *sense = cmd->sense_buf;
struct fbo_state *state = tcmur_dev_get_private(dev);
bool do_verify = false;
int ret;
/* Check for format in progress */
/* Certain commands can be executed even if a format is in progress */
if (state->flags & FBO_FORMATTING &&
cdb[0] != INQUIRY &&
cdb[0] != REQUEST_SENSE &&
cdb[0] != GET_CONFIGURATION &&
cdb[0] != GPCMD_GET_EVENT_STATUS_NOTIFICATION) {
tcmu_sense_set_key_specific_info(sense, state->format_progress);
ret = TCMU_STS_FRMT_IN_PROGRESS;
return ret;
}
switch(cdb[0]) {
case TEST_UNIT_READY:
ret = tcmu_emulate_test_unit_ready(cdb, iovec, iov_cnt);
break;
case REQUEST_SENSE:
ret = fbo_emulate_request_sense(dev, cdb, iovec, iov_cnt, sense);
break;
case FORMAT_UNIT:
ret = fbo_emulate_format_unit(dev, cdb, iovec, iov_cnt, sense);
break;
case READ_6:
case READ_10:
case READ_12:
ret = fbo_read(dev, cdb, iovec, iov_cnt, sense);
break;
case WRITE_VERIFY:
do_verify = true;
case WRITE_6:
case WRITE_10:
case WRITE_12:
ret = fbo_write(dev, cdb, iovec, iov_cnt, sense, do_verify);
break;
case INQUIRY:
ret = fbo_emulate_inquiry(cdb, iovec, iov_cnt, sense);
break;
case MODE_SELECT:
case MODE_SELECT_10:
ret = fbo_emulate_mode_select(cdb, iovec, iov_cnt, sense);
break;
case MODE_SENSE:
case MODE_SENSE_10:
ret = fbo_emulate_mode_sense(cdb, iovec, iov_cnt, sense);
break;
case START_STOP:
ret = tcmu_emulate_start_stop(dev, cdb);
break;
case ALLOW_MEDIUM_REMOVAL:
ret = fbo_emulate_allow_medium_removal(dev, cdb, sense);
break;
case READ_FORMAT_CAPACITIES:
ret = fbo_emulate_read_format_capacities(dev, cdb, iovec,
iov_cnt, sense);
break;
case READ_CAPACITY:
if ((cdb[1] & 0x01) || (cdb[8] & 0x01))
/* Reserved bits for MM logical units */
return TCMU_STS_INVALID_CDB;
else
ret = tcmu_emulate_read_capacity_10(state->num_lbas,
state->block_size,
cdb, iovec,
iov_cnt);
break;
case VERIFY:
ret = fbo_verify(dev, cdb, iovec, iov_cnt, sense);
break;
case SYNCHRONIZE_CACHE:
ret = fbo_synchronize_cache(dev, cdb, sense);
break;
case READ_TOC:
ret = fbo_emulate_read_toc(dev, cdb, iovec, iov_cnt, sense);
break;
case GET_CONFIGURATION:
ret = fbo_emulate_get_configuration(dev, cdb, iovec, iov_cnt,
sense);
break;
case GPCMD_GET_EVENT_STATUS_NOTIFICATION:
ret = fbo_emulate_get_event_status_notification(dev, cdb,
iovec, iov_cnt,
sense);
break;
case READ_DISC_INFORMATION:
ret = fbo_emulate_read_disc_information(dev, cdb, iovec,
iov_cnt, sense);
break;
case READ_DVD_STRUCTURE:
ret = fbo_emulate_read_dvd_structure(dev, cdb, iovec, iov_cnt,
sense);
break;
case MECHANISM_STATUS:
ret = fbo_emulate_mechanism_status(dev, cdb, iovec, iov_cnt,
sense);
break;
default:
ret = TCMU_STS_NOT_HANDLED;
}
return ret;
}
实现方式二 仅实现具体的IO操作
以 file.example.c为例
/*
* Copyright (c) 2014 Red Hat, Inc.
*
* This file is licensed to you under your choice of the GNU Lesser
* General Public License, version 2.1 or any later version (LGPLv2.1 or
* later), or the Apache License 2.0.
*/
/*
* Example code to demonstrate how a TCMU handler might work.
*
* Using the example of backing a device by a file to demonstrate:
*
* 1) Registering with tcmu-runner
* 2) Parsing the handler-specific config string as needed for setup
* 3) Opening resources as needed
* 4) Handling SCSI commands and using the handler API
*/
#define _GNU_SOURCE
#include <stddef.h>
#include <stdint.h>
#include <stdbool.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <endian.h>
#include <errno.h>
#include <scsi/scsi.h>
#include "scsi_defs.h"
#include "libtcmu.h"
#include "tcmu-runner.h"
#include "tcmur_device.h"
struct file_state {
int fd;
};
static int file_open(struct tcmu_device *dev, bool reopen)
{
struct file_state *state;
char *config;
state = calloc(1, sizeof(*state));
if (!state)
return -ENOMEM;
// Init the file state of tcmu
tcmur_dev_set_private(dev, state);
// Move the pointer to the first '/' location in path string
config = strchr(tcmu_dev_get_cfgstring(dev), '/');
if (!config) {
tcmu_err("no configuration found in cfgstring\n");
goto err;
}
config += 1; /* get past '/' */
// Enable the tcmu write cache.(Set the value of tcmu_device as true)
tcmu_dev_set_write_cache_enabled(dev, 1);
// Open the file with path.(With mode)
state->fd = open(config, O_CREAT | O_RDWR, S_IRUSR | S_IWUSR);
if (state->fd == -1) {
tcmu_err("could not open %s: %m\n", config);
goto err;
}
tcmu_dbg("config %s\n", tcmu_dev_get_cfgstring(dev));
return 0;
err:
free(state);
return -EINVAL;
}
static void file_close(struct tcmu_device *dev)
{
// Get the file state of tcmu_device.
struct file_state *state = tcmur_dev_get_private(dev);
// Close the file
close(state->fd);
// free the state
free(state);
}
/**
*
* @param *dev tcmu device
* @param *cmd Command line interface.(not used in this method)
* @param *iov buffer array to syore the data
* @param iov_cnt buffer array size
* @param length read length
* @param offset start address
*
* return the size of read data.
*/
static int file_read(struct tcmu_device *dev, struct tcmulib_cmd *cmd,
struct iovec *iov, size_t iov_cnt, size_t length,
off_t offset)
{
// Get the file state of tecmu_device
struct file_state *state = tcmur_dev_get_private(dev);
size_t remaining = length;
ssize_t ret;
// Read the file in loop
while (remaining) {
// Read the data and store in the iov array. Return the data size.
ret = preadv(state->fd, iov, iov_cnt, offset);
if (ret < 0) {
tcmu_err("read failed: %m\n");
ret = TCMU_STS_RD_ERR;
goto done;
}
if (ret == 0) {
/* EOF, then zeros the iovecs left */
tcmu_iovec_zero(iov, iov_cnt);
break;
}
// Consume the iov array.
tcmu_iovec_seek(iov, ret);
// Move the offset
offset += ret;
// Change the length and continute to read file.
remaining -= ret;
}
// Read finished, and status is OK.
ret = TCMU_STS_OK;
done:
return ret;
}
/**
*
* @param *dev tcmu device
* @param *cmd Command line interface.(not used in this method)
* @param *iov buffer array to syore the data
* @param iov_cnt buffer array size
* @param length write length
* @param offset start address
*
* return the size of read data.
*/
static int file_write(struct tcmu_device *dev, struct tcmulib_cmd *cmd,
struct iovec *iov, size_t iov_cnt, size_t length,
off_t offset)
{
// Get the file state of tecmu_device
struct file_state *state = tcmur_dev_get_private(dev);
size_t remaining = length;
ssize_t ret;
// Write the file in loop
while (remaining) {
// Wirte the data in the iov array to file. Return the data size.
ret = pwritev(state->fd, iov, iov_cnt, offset);
if (ret < 0) {
tcmu_err("write failed: %m\n");
ret = TCMU_STS_WR_ERR;
goto done;
}
// Consume an inv array.
tcmu_iovec_seek(iov, ret);
// Move the offset.
offset += ret;
// Change the length and continue to write.
remaining -= ret;
}
ret = TCMU_STS_OK;
done:
return ret;
}
static int file_flush(struct tcmu_device *dev, struct tcmulib_cmd *cmd)
{
// Get the file state of tcmu_device.
struct file_state *state = tcmur_dev_get_private(dev);
int ret;
// Sync(Flush) the data in page cache to disk.
if (fsync(state->fd)) {
tcmu_err("sync failed\n");
ret = TCMU_STS_WR_ERR;
goto done;
}
ret = TCMU_STS_OK;
done:
return ret;
}
static int file_reconfig(struct tcmu_device *dev, struct tcmulib_cfg_info *cfg)
{
switch (cfg->type) {
// Extend or Reduce the size of file.
case TCMULIB_CFG_DEV_SIZE:
/*
* TODO - For open/reconfig we should make sure the FS the
* file is on is large enough for the requested size. For
* now assume we can grow the file and return 0.
*/
return 0;
case TCMULIB_CFG_DEV_CFGSTR:
// Handle the write cache.
case TCMULIB_CFG_WRITE_CACHE:
default:
return -EOPNOTSUPP;
}
}
static const char file_cfg_desc[] =
"The path to the file to use as a backstore.";
// Init the tcmu_handler with given static method defined in this class.
static struct tcmur_handler file_handler = {
.cfg_desc = file_cfg_desc,
.reconfig = file_reconfig,
.open = file_open,
.close = file_close,
.read = file_read,
.write = file_write,
.flush = file_flush,
.name = "File-backed Handler (example code)",
.subtype = "file",
.nr_threads = 2,
};
/* Entry point must be named "handler_init". */
int handler_init(void)
{
// Regist the file_handler to running_handler list
return tcmur_register_handler(&file_handler);
}
TCMU 深入
targetcli-fb
A command shell for managing the Linux LIO kernel target
class UserBackedStorageObject(StorageObject):
'''
An interface to configFS storage objects for userspace-backed backstore.
'''
def __init__(self, name, config=None, size=None, wwn=None,
hw_max_sectors=None, control=None, index=None):
'''
@param name: The name of the UserBackedStorageObject.
@type name: string
@param config: user-handler-specific config string.
- e.g. "rbd/machine1@snap4"
@type config: string
@param size: The size of the device to create, in bytes.
@type size: int
@param wwn: T10 WWN Unit Serial, will generate if None
@type wwn: string
@hw_max_sectors: Max sectors per command limit to export to initiators.
@type hw_max_sectors: int
@control: String of control=value tuples separate by a ',' that will
passed to the kernel control file.
@type: string
@return: A UserBackedStorageObject object.
'''
if size is not None:
if config is None:
raise RTSLibError("'size' and 'config' must be set when "
"creating a new UserBackedStorageObject")
if '/' not in config:
raise RTSLibError("'config' must contain a '/' separating subtype "
"from its configuration string")
super(UserBackedStorageObject, self).__init__(name, 'create', index)
try:
self._configure(config, size, wwn, hw_max_sectors, control)
except:
self.delete()
raise
else:
super(UserBackedStorageObject, self).__init__(name, 'lookup', index)
def _configure(self, config, size, wwn, hw_max_sectors, control):
self._check_self()
if ':' in config:
raise RTSLibError("':' not allowed in config string")
self._control("dev_config=%s" % config)
self._control("dev_size=%d" % size)
if hw_max_sectors is not None:
self._control("hw_max_sectors=%s" % hw_max_sectors)
if control is not None:
self._control(control)
self._enable()
super(UserBackedStorageObject, self)._configure(wwn)
def _get_size(self):
self._check_self()
return int(self._parse_info('Size'))
def _get_hw_max_sectors(self):
self._check_self()
return int(self._parse_info('HwMaxSectors'))
def _get_control_tuples(self):
self._check_self()
tuples = []
# 1. max_data_area_mb
val = self._parse_info('MaxDataAreaMB')
if val != "NULL":
tuples.append("max_data_area_mb=%s" % val)
val = self.get_attribute('hw_block_size')
if val != "NULL":
tuples.append("hw_block_size=%s" % val)
# 3. add next ...
return ",".join(tuples)
def _get_config(self):
self._check_self()
val = self._parse_info('Config')
if val == "NULL":
return None
return val
def _get_alua_supported(self):
self._check_self()
return storage_object_get_alua_support_attr(self)
hw_max_sectors = property(_get_hw_max_sectors,
doc="Get the max sectors per command.")
control_tuples = property(_get_control_tuples,
doc="Get the comma separated string containing control=value tuples.")
size = property(_get_size,
doc="Get the size in bytes.")
config = property(_get_config,
doc="Get the TCMU config.")
alua_supported = property(_get_alua_supported,
doc="Returns true if ALUA can be setup. False if not supported.")
def dump(self):
d = super(UserBackedStorageObject, self).dump()
d['wwn'] = self.wwn
d['size'] = self.size
d['config'] = self.config
d['hw_max_sectors'] = self.hw_max_sectors
d['control'] = self.control_tuples
return d
class UIFileIOBackstore(UIBackstore):
def ui_command_create(self, name, file_or_dev, size=None, write_back=None,
sparse=None, wwn=None):
class UIUserBackedBackstore(UIBackstore):
def ui_command_create(self, name, size, cfgstring, wwn=None,
hw_max_sectors=None, control=None):
class UIUserBackedBackstore(UIBackstore):
...
def ui_command_create(self, name, size, cfgstring, wwn=None,
hw_max_sectors=None, control=None):
'''
Creates a User-backed storage object.
SIZE SYNTAX
===========
- If size is an int, it represents a number of bytes.
- If size is a string, the following units can be used:
- B or no unit present for bytes
- k, K, kB, KB for kB (kilobytes)
- m, M, mB, MB for MB (megabytes)
- g, G, gB, GB for GB (gigabytes)
- t, T, tB, TB for TB (terabytes)
'''
size = human_to_bytes(size)
wwn = self.ui_eval_param(wwn, 'string', None)
config = self.handler + "/" + cfgstring
ok, errmsg = self.iface.CheckConfig('(s)', config)
if not ok:
raise ExecutionError("cfgstring invalid: %s" % errmsg)
try:
so = UserBackedStorageObject(name, size=size, config=config,
wwn=wwn, hw_max_sectors=hw_max_sectors,
control=control)
except:
raise ExecutionError("UserBackedStorageObject creation failed.")
ui_so = UIUserBackedStorageObject(so, self)
self.shell.log.info("Created user-backed storage object %s size %d."
% (name, size))
return self.new_node(ui_so)