[PATCH V3] nvmet: add simple file backed ns support

Christoph Hellwig hch at lst.de
Tue May 8 22:53:56 PDT 2018


Hi Chaitanya,

this looks great.  A couple mostly cosmetic comments below:

> +	/* for file backed ns just return */
> +	if (!ns->bdev)
> +		goto out;
>
> @@ -71,6 +77,9 @@ static u16 nvmet_get_smart_log_all(struct nvmet_req *req,
>  
>  	rcu_read_lock();
>  	list_for_each_entry_rcu(ns, &ctrl->subsys->namespaces, dev_link) {
> +		/* for file backed ns just return */

Maybe expand the comment a bit that we don't have the data and this
is the best we can do?

> +static int nvmet_ns_enable_blk(struct nvmet_ns *ns)

s/blk/blkdev/

> +{
> +	int blk_flags = FMODE_READ | FMODE_WRITE;
> +
> +	ns->bdev = blkdev_get_by_path(ns->device_path, blk_flags, NULL);

I'd say just kill the blk_flags variable and pass the flags directly
to blkdev_get_by_path.

> +	if (IS_ERR(ns->bdev)) {
> +		pr_err("failed to open block device %s: (%ld)\n",
> +				ns->device_path, PTR_ERR(ns->bdev));

This is going to be printed everytime we open a file, isn't it?
Maybe skip the error print for the error code we get in the case
where the path actually is a file.

> +		ns->bdev = NULL;
> +		return -1;

This should be

		return PTR_ERR(ns->bdev);

> +	}
> +	ns->size = i_size_read(ns->bdev->bd_inode);
> +	ns->blksize_shift =
> +		blksize_bits(bdev_logical_block_size(ns->bdev));

This fits into a single line:

	ns->blksize_shift = blksize_bits(bdev_logical_block_size(ns->bdev));

> +static int nvmet_ns_enable_file(struct nvmet_ns *ns)
> +{
> +	int ret;
> +	int flags = O_RDWR | O_LARGEFILE | O_DIRECT;
> +	struct kstat stat;
> +
> +	ns->file = filp_open(ns->device_path, flags, 0);

No need for the flags argument either.

> +	if (!ns->file || IS_ERR(ns->file)) {

filp_open never returns NULL, so you just need the IS_ERR check.

> +		pr_err("failed to open file %s: (%ld)\n",
> +				ns->device_path, PTR_ERR(ns->bdev));
> +		ns->file = NULL;
> +		return -1;

This should be:
		return PTR_ERR(ns->bdev);

> +	}
> +
> +	ret = vfs_getattr(&ns->file->f_path,
> +			&stat, STATX_INO, AT_STATX_SYNC_AS_STAT);

I think we want AT_STATX_FORCE_SYNC here, and STATX_INO is the wrong
value to ask for (but no one really looks at ir yet, so for now it
happens to work just fine).

So this should be:

	ret = vfs_getattr(&ns->file->f_path, &stat, STATX_SIZE,
			AT_STATX_FORCE_SYNC);

> +	if (ret)
> +		goto err;
> +
> +	ns->size = stat.size;
> +	ns->blksize_shift = file_inode(ns->file)->i_blkbits;
> +
> +	return ret;
> +err:
> +	fput(ns->file);
> +	ns->size = ns->blksize_shift = 0;

Please use a separate line for each assignment.

> +	ns->file = NULL;
> +
> +	return ret;

No need for the blank line here.

> +static int nvmet_ns_dev_enable(struct nvmet_ns *ns)
> +{
> +	int ret = 0;
> +
> +	ret = nvmet_ns_enable_blk(ns);
> +	if (ret)
> +		ret = nvmet_ns_enable_file(ns);
> +
> +	return ret;

Same here.  Also I suspect this function can be removed and the two
calls just be done in the only caller.

> +static void nvmet_flush_work_file(struct work_struct *work)
> +{
> +	int ret;
> +	struct nvmet_req *req;
> +
> +	req = container_of(work, struct nvmet_req, flush_work);

Maybe:

	struct nvmet_req *req = container_of(work, struct nvmet_req, work);
	int ret;


> +static void nvmet_execute_discard_file(struct nvmet_req *req)
> +{
> +	struct nvme_dsm_range range;
> +	int mode = FALLOC_FL_PUNCH_HOLE | FALLOC_FL_KEEP_SIZE;
> +	loff_t offset;
> +	loff_t len;
> +	int i, ret;
> +
> +	for (i = 0; i <= le32_to_cpu(req->cmd->dsm.nr); i++) {
> +		if (nvmet_copy_from_sgl(req, i * sizeof(range), &range,
> +					sizeof(range)))
> +			break;
> +		offset = le64_to_cpu(range.slba) << req->ns->blksize_shift;
> +		len = le32_to_cpu(range.nlb) << req->ns->blksize_shift;
> +		ret = vfs_fallocate(req->ns->file, mode, offset, len);
> +		if (ret)
> +			break;
> +	}
> +
> +	nvmet_req_complete(req, ret < 0 ? NVME_SC_INTERNAL | NVME_SC_DNR : 0);
> +}

Discard and write zeroes should probably also be exectured using the
work struct as they will usually block.

>  u16 nvmet_parse_io_cmd(struct nvmet_req *req)
>  {
>  	struct nvme_command *cmd = req->cmd;
> @@ -207,20 +341,33 @@ u16 nvmet_parse_io_cmd(struct nvmet_req *req)
>  	switch (cmd->common.opcode) {
>  	case nvme_cmd_read:
>  	case nvme_cmd_write:
> -		req->execute = nvmet_execute_rw;
> +		if (req->ns->file)
> +			req->execute = nvmet_execute_rw_file;
> +		else
> +			req->execute = nvmet_execute_rw;
>  		req->data_len = nvmet_rw_len(req);

Maybe it would be cleaner to have separate parse_block_io_cmd/
parse_file_io_cmd helpers? (I'm not entirely sure, use your own
judgement)

> diff --git a/drivers/nvme/target/nvmet.h b/drivers/nvme/target/nvmet.h
> index 15fd84a..60f399f 100644
> --- a/drivers/nvme/target/nvmet.h
> +++ b/drivers/nvme/target/nvmet.h
> @@ -26,6 +26,8 @@
>  #include <linux/configfs.h>
>  #include <linux/rcupdate.h>
>  #include <linux/blkdev.h>
> +#include <linux/file.h>
> +#include <linux/falloc.h>

We shouldn't really need these in nvmet.h, only in io-cmd.c, with
possible an empty forward declaration for struct file here.

> @@ -224,6 +227,8 @@ struct nvmet_req {
>  	struct scatterlist	*sg;
>  	struct bio		inline_bio;
>  	struct bio_vec		inline_bvec[NVMET_MAX_INLINE_BIOVEC];
> +	struct kiocb		iocb;
> +	struct bio_vec		*bvec;
>  	int			sg_cnt;
>  	/* data length as parsed from the command: */
>  	size_t			data_len;
> @@ -234,6 +239,7 @@ struct nvmet_req {
>  
>  	void (*execute)(struct nvmet_req *req);
>  	const struct nvmet_fabrics_ops *ops;
> +	struct work_struct	flush_work;

I think we want a union between the block and file fields to
not bloat the structure, e.g.

	union {
		struct {
			struct bio	inline_bio;
		} b;
		struct {
			struct kiocb	iocb;
			struct bio_vec	*bvev;
			struct work_struct work;
		} f;
	};

plus auditing for other files only used in one path.



More information about the Linux-nvme mailing list