cregit-Linux how code gets into the kernel

Release 4.11 net/9p/client.c

Directory: net/9p
/*
 * net/9p/clnt.c
 *
 * 9P Client
 *
 *  Copyright (C) 2008 by Eric Van Hensbergen <ericvh@gmail.com>
 *  Copyright (C) 2007 by Latchesar Ionkov <lucho@ionkov.net>
 *
 *  This program is free software; you can redistribute it and/or modify
 *  it under the terms of the GNU General Public License version 2
 *  as published by the Free Software Foundation.
 *
 *  This program is distributed in the hope that it will be useful,
 *  but WITHOUT ANY WARRANTY; without even the implied warranty of
 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *  GNU General Public License for more details.
 *
 *  You should have received a copy of the GNU General Public License
 *  along with this program; if not, write to:
 *  Free Software Foundation
 *  51 Franklin Street, Fifth Floor
 *  Boston, MA  02111-1301  USA
 *
 */


#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 <net/9p/9p.h>
#include <linux/parser.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
  */

enum {
	
Opt_msize,
	
Opt_trans,
	
Opt_legacy,
	
Opt_version,
	
Opt_err,
};


static const match_table_t tokens = {
	{Opt_msize, "msize=%u"},
	{Opt_legacy, "noextend"},
	{Opt_trans, "trans=%s"},
	{Opt_version, "version=%s"},
	{Opt_err, NULL},
};


inline int p9_is_proto_dotl(struct p9_client *clnt) { return clnt->proto_version == p9_proto_2000L; }

Contributors

PersonTokensPropCommitsCommitProp
Sripathi Kodi18100.00%2100.00%
Total18100.00%2100.00%

EXPORT_SYMBOL(p9_is_proto_dotl);
inline int p9_is_proto_dotu(struct p9_client *clnt) { return clnt->proto_version == p9_proto_2000u; }

Contributors

PersonTokensPropCommitsCommitProp
Sripathi Kodi18100.00%1100.00%
Total18100.00%1100.00%

EXPORT_SYMBOL(p9_is_proto_dotu); /* * 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; }

Contributors

PersonTokensPropCommitsCommitProp
Simon Derr42100.00%1100.00%
Total42100.00%1100.00%

/* Interpret mount option for protocol version */
static int get_protocol_version(char *s) { int version = -EINVAL; if (!strcmp(s, "9p2000")) { version = p9_proto_legacy; p9_debug(P9_DEBUG_9P, "Protocol version: Legacy\n"); } else if (!strcmp(s, "9p2000.u")) { version = p9_proto_2000u; p9_debug(P9_DEBUG_9P, "Protocol version: 9P2000.u\n"); } else if (!strcmp(s, "9p2000.L")) { version = p9_proto_2000L; p9_debug(P9_DEBUG_9P, "Protocol version: 9P2000.L\n"); } else pr_info("Unknown protocol version %s\n", s); return version; }

Contributors

PersonTokensPropCommitsCommitProp
Sripathi Kodi7980.61%240.00%
Prem Karat1212.24%120.00%
Joe Perches55.10%120.00%
Dan Carpenter22.04%120.00%
Total98100.00%5100.00%

/** * parse_options - parse mount options into client structure * @opts: options string passed from mount * @clnt: existing v9fs client information * * Return 0 upon success, -ERRNO upon failure */
static int parse_opts(char *opts, struct p9_client *clnt) { char *options, *tmp_options; char *p; substring_t args[MAX_OPT_ARGS]; int option; char *s; int ret = 0; clnt->proto_version = p9_proto_2000L; clnt->msize = 8192; if (!opts) return 0; tmp_options = kstrdup(opts, GFP_KERNEL); if (!tmp_options) { p9_debug(P9_DEBUG_ERROR, "failed to allocate copy of option string\n"); return -ENOMEM; } options = tmp_options; while ((p = strsep(&options, ",")) != NULL) { int token, r; if (!*p) continue; token = match_token(p, tokens, args); switch (token) { case Opt_msize: r = match_int(&args[0], &option); if (r < 0) { p9_debug(P9_DEBUG_ERROR, "integer field, but no integer?\n"); ret = r; continue; } clnt->msize = option; break; case Opt_trans: s = match_strdup(&args[0]); if (!s) { ret = -ENOMEM; p9_debug(P9_DEBUG_ERROR, "problem allocating copy of trans arg\n"); goto free_and_return; } clnt->trans_mod = v9fs_get_trans_by_name(s); if (clnt->trans_mod == NULL) { pr_info("Could not find request transport: %s\n", s); ret = -EINVAL; kfree(s); goto free_and_return; } kfree(s); break; case Opt_legacy: clnt->proto_version = p9_proto_legacy; break; case Opt_version: s = match_strdup(&args[0]); if (!s) { ret = -ENOMEM; p9_debug(P9_DEBUG_ERROR, "problem allocating copy of version arg\n"); goto free_and_return; } ret = get_protocol_version(s); if (ret == -EINVAL) { kfree(s); goto free_and_return; } kfree(s); clnt->proto_version = ret; break; default: continue; } } free_and_return: kfree(tmp_options); return ret; }

Contributors

PersonTokensPropCommitsCommitProp
Eric Van Hensbergen22562.33%440.00%
Prem Karat8924.65%110.00%
Sripathi Kodi328.86%220.00%
Aneesh Kumar K.V92.49%220.00%
Joe Perches61.66%110.00%
Total361100.00%10100.00%


static struct p9_fcall *p9_fcall_alloc(int alloc_msize) { struct p9_fcall *fc; fc = kmalloc(sizeof(struct p9_fcall) + alloc_msize, GFP_NOFS); if (!fc) return NULL; fc->capacity = alloc_msize; fc->sdata = (char *) fc + sizeof(struct p9_fcall); return fc; }

Contributors

PersonTokensPropCommitsCommitProp
Simon Derr6398.44%150.00%
Rashika Kheria11.56%150.00%
Total64100.00%2100.00%

/** * p9_tag_alloc - lookup/allocate a request by tag * @c: client session to lookup tag within * @tag: numeric id for transaction * * this is a simple array lookup, but will grow the * request_slots as necessary to accommodate transaction * ids which did not previously have a slot. * * this code relies on the client spinlock to manage locks, its * possible we should switch to something else, but I'd rather * stick with something low-overhead for the common case. * */
static struct p9_req_t * p9_tag_alloc(struct p9_client *c, u16 tag, unsigned int max_size) { unsigned long flags; int row, col; struct p9_req_t *req; int alloc_msize = min(c->msize, max_size); /* This looks up the original request by tag so we know which * buffer to read the data into */ tag++; if (tag >= c->max_tag) { spin_lock_irqsave(&c->lock, flags); /* check again since original check was outside of lock */ while (tag >= c->max_tag) { row = (tag / P9_ROW_MAXTAG); c->reqs[row] = kcalloc(P9_ROW_MAXTAG, sizeof(struct p9_req_t), GFP_ATOMIC); if (!c->reqs[row]) { pr_err("Couldn't grow tag array\n"); spin_unlock_irqrestore(&c->lock, flags); return ERR_PTR(-ENOMEM); } for (col = 0; col < P9_ROW_MAXTAG; col++) { c->reqs[row][col].status = REQ_STATUS_IDLE; c->reqs[row][col].tc = NULL; } c->max_tag += P9_ROW_MAXTAG; } spin_unlock_irqrestore(&c->lock, flags); } row = tag / P9_ROW_MAXTAG; col = tag % P9_ROW_MAXTAG; req = &c->reqs[row][col]; if (!req->wq) { req->wq = kmalloc(sizeof(wait_queue_head_t), GFP_NOFS); if (!req->wq) goto grow_failed; init_waitqueue_head(req->wq); } if (!req->tc) req->tc = p9_fcall_alloc(alloc_msize); if (!req->rc) req->rc = p9_fcall_alloc(alloc_msize); if (!req->tc || !req->rc) goto grow_failed; p9pdu_reset(req->tc); p9pdu_reset(req->rc); req->tc->tag = tag-1; req->status = REQ_STATUS_ALLOC; return req; grow_failed: pr_err("Couldn't grow tag array\n"); kfree(req->tc); kfree(req->rc); kfree(req->wq); req->tc = req->rc = NULL; req->wq = NULL; return ERR_PTR(-ENOMEM); }

Contributors

PersonTokensPropCommitsCommitProp
Eric Van Hensbergen29273.37%330.00%
Simon Derr8220.60%220.00%
Aneesh Kumar K.V164.02%220.00%
Dan Carpenter71.76%220.00%
Joe Perches10.25%110.00%
Total398100.00%10100.00%

/** * p9_tag_lookup - lookup a request by tag * @c: client session to lookup tag within * @tag: numeric id for transaction * */
struct p9_req_t *p9_tag_lookup(struct p9_client *c, u16 tag) { int row, col; /* This looks up the original request by tag so we know which * buffer to read the data into */ tag++; if(tag >= c->max_tag) return NULL; row = tag / P9_ROW_MAXTAG; col = tag % P9_ROW_MAXTAG; return &c->reqs[row][col]; }

Contributors

PersonTokensPropCommitsCommitProp
Eric Van Hensbergen59100.00%2100.00%
Total59100.00%2100.00%

EXPORT_SYMBOL(p9_tag_lookup); /** * p9_tag_init - setup tags structure and contents * @c: v9fs client struct * * This initializes the tags structure for each client instance. * */
static int p9_tag_init(struct p9_client *c) { int err = 0; c->tagpool = p9_idpool_create(); if (IS_ERR(c->tagpool)) { err = PTR_ERR(c->tagpool); goto error; } err = p9_idpool_get(c->tagpool); /* reserve tag 0 */ if (err < 0) { p9_idpool_destroy(c->tagpool); goto error; } c->max_tag = 0; error: return err; }

Contributors

PersonTokensPropCommitsCommitProp
Eric Van Hensbergen6576.47%150.00%
Aneesh Kumar K.V2023.53%150.00%
Total85100.00%2100.00%

/** * 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) { int row, col; /* check to insure all requests are idle */ for (row = 0; row < (c->max_tag/P9_ROW_MAXTAG); row++) { for (col = 0; col < P9_ROW_MAXTAG; col++) { if (c->reqs[row][col].status != REQ_STATUS_IDLE) { p9_debug(P9_DEBUG_MUX, "Attempting to cleanup non-free tag %d,%d\n", row, col); /* TODO: delay execution of cleanup */ return; } } } if (c->tagpool) { p9_idpool_put(0, c->tagpool); /* free reserved tag 0 */ p9_idpool_destroy(c->tagpool); } /* free requests associated with tags */ for (row = 0; row < (c->max_tag/P9_ROW_MAXTAG); row++) { for (col = 0; col < P9_ROW_MAXTAG; col++) { kfree(c->reqs[row][col].wq); kfree(c->reqs[row][col].tc); kfree(c->reqs[row][col].rc); } kfree(c->reqs[row]); } c->max_tag = 0; }

Contributors

PersonTokensPropCommitsCommitProp
Eric Van Hensbergen19493.72%360.00%
Latchesar Ionkov125.80%120.00%
Joe Perches10.48%120.00%
Total207100.00%5100.00%

/** * p9_free_req - free a request and clean-up as necessary * c: client state * r: request to release * */
static void p9_free_req(struct p9_client *c, struct p9_req_t *r) { int tag = r->tc->tag; p9_debug(P9_DEBUG_MUX, "clnt %p req %p tag: %d\n", c, r, tag); r->status = REQ_STATUS_IDLE; if (tag != P9_NOTAG && p9_idpool_check(tag, c->tagpool)) p9_idpool_put(tag, c->tagpool); }

Contributors

PersonTokensPropCommitsCommitProp
Eric Van Hensbergen6798.53%266.67%
Joe Perches11.47%133.33%
Total68100.00%3100.00%

/** * p9_client_cb - call back from transport to client * c: client state * req: request received * */
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 other thread wakes up will indeed be seen by the waiting side. */ smp_wmb(); req->status = status; wake_up(req->wq); p9_debug(P9_DEBUG_MUX, "wakeup: %d\n", req->tc->tag); }

Contributors

PersonTokensPropCommitsCommitProp
Eric Van Hensbergen4370.49%457.14%
Dominique Martinet1321.31%114.29%
Latchesar Ionkov34.92%114.29%
Joe Perches23.28%114.29%
Total61100.00%7100.00%

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 */
int p9_parse_header(struct p9_fcall *pdu, int32_t *size, int8_t *type, int16_t *tag, int rewind) { int8_t r_type; int16_t r_tag; int32_t r_size; int offset = pdu->offset; int err; pdu->offset = 0; if (pdu->size == 0) pdu->size = 7; err = p9pdu_readf(pdu, 0, "dbw", &r_size, &r_type, &r_tag); if (err) goto rewind_and_exit; pdu->size = r_size; 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); if (type) *type = r_type; if (tag) *tag = r_tag; if (size) *size = r_size; rewind_and_exit: if (rewind) pdu->offset = offset; return err; }

Contributors

PersonTokensPropCommitsCommitProp
Eric Van Hensbergen16999.41%375.00%
Joe Perches10.59%125.00%
Total170100.00%4100.00%

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) { int8_t type; int err; int ecode; err = p9_parse_header(req->rc, NULL, &type, NULL, 0); /* * 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; err = p9pdu_readf(req->rc, c->proto_version, "s?d", &ename, &ecode); if (err) 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); 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; }

Contributors

PersonTokensPropCommitsCommitProp
Eric Van Hensbergen13658.87%222.22%
Arun R Bharadwaj6327.27%111.11%
Aneesh Kumar K.V114.76%222.22%
Venkateswararao Jujjuri (JV)93.90%111.11%
Joe Perches41.73%111.11%
Sripathi Kodi41.73%111.11%
Arnd Bergmann41.73%111.11%
Total231100.00%9100.00%

/** * p9_check_zc_errors - check 9p packet for error return and process it * @c: current client instance * @req: request to parse and check for error conditions * @in_hdrlen: Size of response protocol buffer. * * 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_zc_errors(struct p9_client *c, struct p9_req_t *req, struct iov_iter *uidata, int in_hdrlen) { int err; int ecode; int8_t type; char *ename = NULL; err = p9_parse_header(req->rc, NULL, &type, NULL, 0); /* * dump the response from server * This should be after parse_header 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)) { /* Error is reported in string format */ int len; /* 7 = header size for RERROR; */ int inline_len = in_hdrlen - 7; len = req->rc->size - req->rc->offset; if (len > (P9_ZC_HDR_SZ - 7)) { err = -EFAULT; goto out_err; } ename = &req->rc->sdata[req->rc->offset]; if (len > inline_len) { /* We have error in external buffer */ err = copy_from_iter(ename + inline_len, len - inline_len, uidata); if (err != len - inline_len) { err = -EFAULT; goto out_err; } } ename = NULL; err = p9pdu_readf(req->rc, c->proto_version, "s?d", &ename, &ecode); if (err) 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); 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; }

Contributors

PersonTokensPropCommitsCommitProp
Aneesh Kumar K.V29183.38%337.50%
Eric Van Hensbergen4111.75%225.00%
Al Viro92.58%112.50%
Joe Perches41.15%112.50%
Arnd Bergmann41.15%112.50%
Total349100.00%8100.00%

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; int16_t 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 (oldreq->status == REQ_STATUS_SENT) if (c->trans_mod->cancelled) c->trans_mod->cancelled(c, oldreq); p9_free_req(c, req); return 0; }

Contributors

PersonTokensPropCommitsCommitProp
Aneesh Kumar K.V10584.00%116.67%
Simon Derr1713.60%350.00%
Andi Shyti21.60%116.67%
Joe Perches10.80%116.67%
Total125100.00%6100.00%


static struct p9_req_t *p9_client_prepare_req(struct p9_client *c, int8_t type, int req_size, const char *fmt, va_list ap) { int tag, err; struct p9_req_t *req; p9_debug(P9_DEBUG_MUX, "client %p op %d\n", c, type); /* we allow for any status other than disconnected */ if (c->status == Disconnected) return ERR_PTR(-EIO); /* if status is begin_disconnected we allow only clunk request */ if ((c->status == BeginDisconnect) && (type != P9_TCLUNK)) return ERR_PTR(-EIO); tag = P9_NOTAG; if (type != P9_TVERSION) { tag = p9_idpool_get(c->tagpool); if (tag < 0) return ERR_PTR(-ENOMEM); } req = p9_tag_alloc(c, tag, req_size); if (IS_ERR(req)) return req; /* marshall the data */ p9pdu_prepare(req->tc, tag, type); err = p9pdu_vwritef(req->tc, c->proto_version, fmt, ap); if (err) goto reterr; p9pdu_finalize(c, req->tc); trace_9p_client_req(c, type, tag); return req; reterr: p9_free_req(c, req); return ERR_PTR(err); }

Contributors

PersonTokensPropCommitsCommitProp
Aneesh Kumar K.V21499.53%266.67%
Joe Perches10.47%133.33%
Total215100.00%3100.00%

/** * p9_client_rpc - issue a request and wait for a response * @c: client session * @type: type of request * @fmt: protocol format string (see protocol.c) * * Returns request structure (which client must free using p9_free_req) */
static struct p9_req_t * p9_client_rpc(struct p9_client *c, int8_t type, const char *fmt, ...) { va_list ap; int sigpending, err; unsigned long flags; struct p9_req_t *req; va_start(ap, fmt); req = p9_client_prepare_req(c, type, c->msize, fmt, ap); va_end(ap); if (IS_ERR(req)) return req; if (signal_pending(current)) { sigpending = 1; clear_thread_flag(TIF_SIGPENDING); } else sigpending = 0; err = c->trans_mod->request(c, req); if (err < 0) { if (err != -ERESTARTSYS && err != -EFAULT) c->status = Disconnected; goto reterr; } again: /* Wait for the response */ err = wait_event_interruptible(*req->wq, req->status >= REQ_STATUS_RCVD); /* * Make sure our req is coherent with regard to updates in other * threads - echoes to wmb() in the callback */ smp_rmb(); if ((err == -ERESTARTSYS) && (c->status == Connected) && (type == P9_TFLUSH)) { sigpending = 1; clear_thread_flag(TIF_SIGPENDING); goto again; } if (req->status == REQ_STATUS_ERROR) { p9_debug(P9_DEBUG_ERROR, "req_status error %d\n", req->t_err); err = req->t_err; } if ((err == -ERESTARTSYS) && (c->status == Connected