2212 lines · 52.3 KB · C
RawDownload
// SPDX-License-Identifier: GPL-2.0-only/* * 9P Client * *  Copyright (C) 2008 by Eric Van Hensbergen <ericvh@gmail.com> *  Copyright (C) 2007 by Latchesar Ionkov <lucho@ionkov.net> */ #define pr_fmt(fmt) KBUILD_MODNAME ": " fmt #include <linux/module.h>#include <linux/errno.h>#include <linux/fs.h>#include <linux/poll.h>#include <linux/idr.h>#include <linux/mutex.h>#include <linux/slab.h>#include <linux/sched/signal.h>#include <linux/uaccess.h>#include <linux/uio.h>#include <linux/netfs.h>#include <net/9p/9p.h>#include <linux/seq_file.h>#include <linux/fs_context.h>#include <net/9p/client.h>#include <net/9p/transport.h>#include "protocol.h" #define CREATE_TRACE_POINTS#include <trace/events/9p.h> /* Client Option Parsing (code inspired by NFS code) *  - a little lazy - parse all client options */ inline int p9_is_proto_dotl(struct p9_client *clnt){	return clnt->proto_version == p9_proto_2000L;}EXPORT_SYMBOL(p9_is_proto_dotl); inline int p9_is_proto_dotu(struct p9_client *clnt){	return clnt->proto_version == p9_proto_2000u;}EXPORT_SYMBOL(p9_is_proto_dotu); int p9_show_client_options(struct seq_file *m, struct p9_client *clnt){	if (clnt->msize != DEFAULT_MSIZE)		seq_printf(m, ",msize=%u", clnt->msize);	seq_printf(m, ",trans=%s", clnt->trans_mod->name); 	switch (clnt->proto_version) {	case p9_proto_legacy:		seq_puts(m, ",noextend");		break;	case p9_proto_2000u:		seq_puts(m, ",version=9p2000.u");		break;	case p9_proto_2000L:		/* Default */		break;	} 	if (clnt->trans_mod->show_options)		return clnt->trans_mod->show_options(m, clnt);	return 0;}EXPORT_SYMBOL(p9_show_client_options); /* Some error codes are taken directly from the server replies, * make sure they are valid. */static int safe_errno(int err){	if (err > 0 || err < -MAX_ERRNO) {		p9_debug(P9_DEBUG_ERROR, "Invalid error code %d\n", err);		return -EPROTO;	}	return err;} static int apply_client_options(struct p9_client *clnt, struct fs_context *fc){	struct v9fs_context *ctx = fc->fs_private; 	clnt->msize = ctx->client_opts.msize;	clnt->trans_mod = ctx->client_opts.trans_mod;	ctx->client_opts.trans_mod = NULL;	clnt->proto_version = ctx->client_opts.proto_version; 	return 0;} static int p9_fcall_init(struct p9_client *c, struct p9_fcall *fc,			 int alloc_msize){	if (likely(c->fcall_cache) && alloc_msize == c->msize) {		fc->sdata = kmem_cache_alloc(c->fcall_cache, GFP_NOFS);		fc->cache = c->fcall_cache;		if (!fc->sdata && c->trans_mod->supports_vmalloc) {			fc->sdata = kvmalloc(alloc_msize, GFP_NOFS);			fc->cache = NULL;		}	} else {		if (c->trans_mod->supports_vmalloc)			fc->sdata = kvmalloc(alloc_msize, GFP_NOFS);		else			fc->sdata = kmalloc(alloc_msize, GFP_NOFS);		fc->cache = NULL;	}	if (!fc->sdata)		return -ENOMEM;	fc->capacity = alloc_msize;	fc->id = 0;	fc->tag = P9_NOTAG;	return 0;} void p9_fcall_fini(struct p9_fcall *fc){	/* sdata can be NULL for interrupted requests in trans_rdma,	 * and kmem_cache_free does not do NULL-check for us	 */	if (unlikely(!fc->sdata))		return; 	if (fc->cache)		kmem_cache_free(fc->cache, fc->sdata);	else		kvfree(fc->sdata);}EXPORT_SYMBOL(p9_fcall_fini); static struct kmem_cache *p9_req_cache; /** * p9_tag_alloc - Allocate a new request. * @c: Client session. * @type: Transaction type. * @t_size: Buffer size for holding this request * (automatic calculation by format template if 0). * @r_size: Buffer size for holding server's reply on this request * (automatic calculation by format template if 0). * @fmt: Format template for assembling 9p request message * (see p9pdu_vwritef). * @ap: Variable arguments to be fed to passed format template * (see p9pdu_vwritef). * * Context: Process context. * Return: Pointer to new request. */static struct p9_req_t *p9_tag_alloc(struct p9_client *c, int8_t type, uint t_size, uint r_size,	      const char *fmt, va_list ap){	struct p9_req_t *req = kmem_cache_alloc(p9_req_cache, GFP_NOFS);	int alloc_tsize;	int alloc_rsize;	int tag;	va_list apc; 	va_copy(apc, ap);	alloc_tsize = min_t(size_t, c->msize,			    t_size ?: p9_msg_buf_size(c, type, fmt, apc));	va_end(apc); 	alloc_rsize = min_t(size_t, c->msize,			    r_size ?: p9_msg_buf_size(c, type + 1, fmt, ap)); 	if (!req)		return ERR_PTR(-ENOMEM); 	if (p9_fcall_init(c, &req->tc, alloc_tsize))		goto free_req;	if (p9_fcall_init(c, &req->rc, alloc_rsize))		goto free; 	p9pdu_reset(&req->tc);	p9pdu_reset(&req->rc);	req->t_err = 0;	req->status = REQ_STATUS_ALLOC;	/* refcount needs to be set to 0 before inserting into the idr	 * so p9_tag_lookup does not accept a request that is not fully	 * initialized. refcount_set to 2 below will mark request ready.	 */	refcount_set(&req->refcount, 0);	init_waitqueue_head(&req->wq);	INIT_LIST_HEAD(&req->req_list); 	idr_preload(GFP_NOFS);	spin_lock_irq(&c->lock);	if (type == P9_TVERSION)		tag = idr_alloc(&c->reqs, req, P9_NOTAG, P9_NOTAG + 1,				GFP_NOWAIT);	else		tag = idr_alloc(&c->reqs, req, 0, P9_NOTAG, GFP_NOWAIT);	req->tc.tag = tag;	spin_unlock_irq(&c->lock);	idr_preload_end();	if (tag < 0)		goto free; 	/* Init ref to two because in the general case there is one ref	 * that is put asynchronously by a writer thread, one ref	 * temporarily given by p9_tag_lookup and put by p9_client_cb	 * in the recv thread, and one ref put by p9_req_put in the	 * main thread. The only exception is virtio that does not use	 * p9_tag_lookup but does not have a writer thread either	 * (the write happens synchronously in the request/zc_request	 * callback), so p9_client_cb eats the second ref there	 * as the pointer is duplicated directly by virtqueue_add_sgs()	 */	refcount_set(&req->refcount, 2); 	return req; free:	p9_fcall_fini(&req->tc);	p9_fcall_fini(&req->rc);free_req:	kmem_cache_free(p9_req_cache, req);	return ERR_PTR(-ENOMEM);} /** * p9_tag_lookup - Look up a request by tag. * @c: Client session. * @tag: Transaction ID. * * Context: Any context. * Return: A request, or %NULL if there is no request with that tag. */struct p9_req_t *p9_tag_lookup(struct p9_client *c, u16 tag){	struct p9_req_t *req; 	rcu_read_lock();again:	req = idr_find(&c->reqs, tag);	if (req) {		/* We have to be careful with the req found under rcu_read_lock		 * Thanks to SLAB_TYPESAFE_BY_RCU we can safely try to get the		 * ref again without corrupting other data, then check again		 * that the tag matches once we have the ref		 */		if (!p9_req_try_get(req))			goto again;		if (req->tc.tag != tag) {			p9_req_put(c, req);			goto again;		}	}	rcu_read_unlock(); 	return req;}EXPORT_SYMBOL(p9_tag_lookup); /** * p9_tag_remove - Remove a tag. * @c: Client session. * @r: Request of reference. * * Context: Any context. */static void p9_tag_remove(struct p9_client *c, struct p9_req_t *r){	unsigned long flags;	u16 tag = r->tc.tag; 	p9_debug(P9_DEBUG_MUX, "freeing clnt %p req %p tag: %d\n", c, r, tag);	spin_lock_irqsave(&c->lock, flags);	idr_remove(&c->reqs, tag);	spin_unlock_irqrestore(&c->lock, flags);} int p9_req_put(struct p9_client *c, struct p9_req_t *r){	if (refcount_dec_and_test(&r->refcount)) {		p9_tag_remove(c, r); 		p9_fcall_fini(&r->tc);		p9_fcall_fini(&r->rc);		kmem_cache_free(p9_req_cache, r);		return 1;	}	return 0;}EXPORT_SYMBOL(p9_req_put); /** * p9_tag_cleanup - cleans up tags structure and reclaims resources * @c:  v9fs client struct * * This frees resources associated with the tags structure * */static void p9_tag_cleanup(struct p9_client *c){	struct p9_req_t *req;	int id; 	rcu_read_lock();	idr_for_each_entry(&c->reqs, req, id) {		pr_info("Tag %d still in use\n", id);		if (p9_req_put(c, req) == 0)			pr_warn("Packet with tag %d has still references",				req->tc.tag);	}	rcu_read_unlock();} /** * p9_client_cb - call back from transport to client * @c: client state * @req: request received * @status: request status, one of REQ_STATUS_* * */void p9_client_cb(struct p9_client *c, struct p9_req_t *req, int status){	p9_debug(P9_DEBUG_MUX, " tag %d\n", req->tc.tag); 	/* This barrier is needed to make sure any change made to req before	 * the status change is visible to another thread	 */	smp_wmb();	WRITE_ONCE(req->status, status); 	wake_up(&req->wq);	p9_debug(P9_DEBUG_MUX, "wakeup: %d\n", req->tc.tag);	p9_req_put(c, req);}EXPORT_SYMBOL(p9_client_cb); /** * p9_parse_header - parse header arguments out of a packet * @pdu: packet to parse * @size: size of packet * @type: type of request * @tag: tag of packet * @rewind: set if we need to rewind offset afterwards */ intp9_parse_header(struct p9_fcall *pdu, int32_t *size, int8_t *type,		int16_t *tag, int rewind){	s8 r_type;	s16 r_tag;	s32 r_size;	int offset = pdu->offset;	int err; 	pdu->offset = 0; 	err = p9pdu_readf(pdu, 0, "dbw", &r_size, &r_type, &r_tag);	if (err)		goto rewind_and_exit; 	if (type)		*type = r_type;	if (tag)		*tag = r_tag;	if (size)		*size = r_size; 	if (pdu->size != r_size || r_size < 7) {		err = -EINVAL;		goto rewind_and_exit;	} 	pdu->id = r_type;	pdu->tag = r_tag; 	p9_debug(P9_DEBUG_9P, "<<< size=%d type: %d tag: %d\n",		 pdu->size, pdu->id, pdu->tag); rewind_and_exit:	if (rewind)		pdu->offset = offset;	return err;}EXPORT_SYMBOL(p9_parse_header); /** * p9_check_errors - check 9p packet for error return and process it * @c: current client instance * @req: request to parse and check for error conditions * * returns error code if one is discovered, otherwise returns 0 * * this will have to be more complicated if we have multiple * error packet types */ static int p9_check_errors(struct p9_client *c, struct p9_req_t *req){	s8 type;	int err;	int ecode; 	err = p9_parse_header(&req->rc, NULL, &type, NULL, 0);	if (req->rc.size > req->rc.capacity && !req->rc.zc) {		pr_err("requested packet size too big: %d does not fit %zu (type=%d)\n",		       req->rc.size, req->rc.capacity, req->rc.id);		return -EIO;	}	/* dump the response from server	 * This should be after check errors which poplulate pdu_fcall.	 */	trace_9p_protocol_dump(c, &req->rc);	if (err) {		p9_debug(P9_DEBUG_ERROR, "couldn't parse header %d\n", err);		return err;	}	if (type != P9_RERROR && type != P9_RLERROR)		return 0; 	if (!p9_is_proto_dotl(c)) {		char *ename = NULL; 		err = p9pdu_readf(&req->rc, c->proto_version, "s?d",				  &ename, &ecode);		if (err) {			kfree(ename);			goto out_err;		} 		if (p9_is_proto_dotu(c) && ecode < 512)			err = -ecode; 		if (!err) {			err = p9_errstr2errno(ename, strlen(ename)); 			p9_debug(P9_DEBUG_9P, "<<< RERROR (%d) %s\n",				 -ecode, ename);		}		kfree(ename);	} else {		err = p9pdu_readf(&req->rc, c->proto_version, "d", &ecode);		if (err)			goto out_err;		err = -ecode; 		p9_debug(P9_DEBUG_9P, "<<< RLERROR (%d)\n", -ecode);	} 	return err; out_err:	p9_debug(P9_DEBUG_ERROR, "couldn't parse error%d\n", err); 	return err;} static struct p9_req_t *p9_client_rpc(struct p9_client *c, int8_t type, const char *fmt, ...); /** * p9_client_flush - flush (cancel) a request * @c: client state * @oldreq: request to cancel * * This sents a flush for a particular request and links * the flush request to the original request.  The current * code only supports a single flush request although the protocol * allows for multiple flush requests to be sent for a single request. * */ static int p9_client_flush(struct p9_client *c, struct p9_req_t *oldreq){	struct p9_req_t *req;	s16 oldtag;	int err; 	err = p9_parse_header(&oldreq->tc, NULL, NULL, &oldtag, 1);	if (err)		return err; 	p9_debug(P9_DEBUG_9P, ">>> TFLUSH tag %d\n", oldtag); 	req = p9_client_rpc(c, P9_TFLUSH, "w", oldtag);	if (IS_ERR(req))		return PTR_ERR(req); 	/* if we haven't received a response for oldreq,	 * remove it from the list	 */	if (READ_ONCE(oldreq->status) == REQ_STATUS_SENT) {		if (c->trans_mod->cancelled)			c->trans_mod->cancelled(c, oldreq);	} 	p9_req_put(c, req);	return 0;}

Lines 1–500 of 2212.

Keyboard shortcuts

General

Show keyboard shortcuts
?
Focus the current search
/

Issues and pull requests

Open filters
f

Issues, pull requests and Inbox

Select next item
j↓
Select previous item
k↑
Open selected item
Enter
Dismiss filters or clear selection
Esc

Repository code

Go to file
t