diff options
Diffstat (limited to 'net/9p/client.c')
-rw-r--r-- | net/9p/client.c | 265 |
1 files changed, 251 insertions, 14 deletions
diff --git a/net/9p/client.c b/net/9p/client.c index f2d07ef9e6a4..29934febecdb 100644 --- a/net/9p/client.c +++ b/net/9p/client.c @@ -55,6 +55,9 @@ static const match_table_t tokens = { {Opt_err, NULL}, }; +static int +p9_client_rpc(struct p9_client *c, struct p9_fcall *tc, struct p9_fcall **rc); + /** * v9fs_parse_options - parse mount options into session structure * @options: options string passed from mount @@ -269,6 +272,36 @@ static void p9_tag_cleanup(struct p9_client *c) } /** + * p9_client_flush - flush (cancel) a request + * c: client state + * req: request to cancel + * + * This sents a flush for a particular requests 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 *req) +{ + struct p9_fcall *tc, *rc = NULL; + int err; + + P9_DPRINTK(P9_DEBUG_9P, "client %p tag %d\n", c, req->tc->tag); + + tc = p9_create_tflush(req->tc->tag); + if (IS_ERR(tc)) + return PTR_ERR(tc); + + err = p9_client_rpc(c, tc, &rc); + + /* we don't free anything here because RPC isn't complete */ + + return err; +} + +/** * p9_free_req - free a request and clean-up as necessary * c: client state * r: request to release @@ -289,6 +322,224 @@ void p9_free_req(struct p9_client *c, struct p9_req_t *r) } } +/** + * 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) +{ + struct p9_req_t *other_req; + unsigned long flags; + + P9_DPRINTK(P9_DEBUG_MUX, ": %d\n", req->tc->tag); + + if (req->status == REQ_STATUS_ERROR) + wake_up(req->wq); + + if (req->tc->id == P9_TFLUSH) { /* flush receive path */ + P9_DPRINTK(P9_DEBUG_MUX, "flush: %d\n", req->tc->tag); + spin_lock_irqsave(&c->lock, flags); + other_req = p9_tag_lookup(c, req->tc->params.tflush.oldtag); + if (other_req->flush_tag != req->tc->tag) /* stale flush */ + spin_unlock_irqrestore(&c->lock, flags); + else { + BUG_ON(other_req->status != REQ_STATUS_FLSH); + other_req->status = REQ_STATUS_FLSHD; + spin_unlock_irqrestore(&c->lock, flags); + wake_up(other_req->wq); + } + p9_free_req(c, req); + } else { /* normal receive path */ + P9_DPRINTK(P9_DEBUG_MUX, "normal: %d\n", req->tc->tag); + spin_lock_irqsave(&c->lock, flags); + if (req->status != REQ_STATUS_FLSHD) + req->status = REQ_STATUS_RCVD; + req->flush_tag = P9_NOTAG; + spin_unlock_irqrestore(&c->lock, flags); + wake_up(req->wq); + P9_DPRINTK(P9_DEBUG_MUX, "wakeup: %d\n", req->tc->tag); + } +} +EXPORT_SYMBOL(p9_client_cb); + +/** + * p9_client_rpc - issue a request and wait for a response + * @c: client session + * @tc: &p9_fcall request to transmit + * @rc: &p9_fcall to put reponse into + * + * Returns 0 on success, error code on failure + */ + +static int +p9_client_rpc(struct p9_client *c, struct p9_fcall *tc, struct p9_fcall **rc) +{ + int tag, err, size; + char *rdata; + struct p9_req_t *req; + unsigned long flags; + int sigpending; + int flushed = 0; + + P9_DPRINTK(P9_DEBUG_9P, "client %p tc %p rc %p\n", c, tc, rc); + + if (c->status != Connected) + return -EIO; + + if (signal_pending(current)) { + sigpending = 1; + clear_thread_flag(TIF_SIGPENDING); + } else + sigpending = 0; + + tag = P9_NOTAG; + if (tc->id != P9_TVERSION) { + tag = p9_idpool_get(c->tagpool); + if (tag < 0) + return -ENOMEM; + } + + req = p9_tag_alloc(c, tag); + + /* if this is a flush request, backlink flush request now to + * avoid race conditions later. */ + if (tc->id == P9_TFLUSH) { + struct p9_req_t *other_req = + p9_tag_lookup(c, tc->params.tflush.oldtag); + if (other_req->status == REQ_STATUS_FLSH) + other_req->flush_tag = tag; + } + + p9_set_tag(tc, tag); + + /* + * if client passed in a pre-allocated response fcall struct + * then we just use that, otherwise we allocate one. + */ + + if (rc == NULL) + req->rc = NULL; + else + req->rc = *rc; + if (req->rc == NULL) { + req->rc = kmalloc(sizeof(struct p9_fcall) + c->msize, + GFP_KERNEL); + if (!req->rc) { + err = -ENOMEM; + p9_idpool_put(tag, c->tagpool); + p9_free_req(c, req); + goto reterr; + } + *rc = req->rc; + } + + rdata = (char *)req->rc+sizeof(struct p9_fcall); + + req->tc = tc; + P9_DPRINTK(P9_DEBUG_9P, "request: tc: %p rc: %p\n", req->tc, req->rc); + + err = c->trans_mod->request(c, req); + if (err < 0) { + c->status = Disconnected; + goto reterr; + } + + /* if it was a flush we just transmitted, return our tag */ + if (tc->id == P9_TFLUSH) + return 0; +again: + P9_DPRINTK(P9_DEBUG_9P, "wait %p tag: %d\n", req->wq, tag); + err = wait_event_interruptible(*req->wq, + req->status >= REQ_STATUS_RCVD); + P9_DPRINTK(P9_DEBUG_9P, "wait %p tag: %d returned %d (flushed=%d)\n", + req->wq, tag, err, flushed); + + if (req->status == REQ_STATUS_ERROR) { + P9_DPRINTK(P9_DEBUG_9P, "req_status error %d\n", req->t_err); + err = req->t_err; + } else if (err == -ERESTARTSYS && flushed) { + P9_DPRINTK(P9_DEBUG_9P, "flushed - going again\n"); + goto again; + } else if (req->status == REQ_STATUS_FLSHD) { + P9_DPRINTK(P9_DEBUG_9P, "flushed - erestartsys\n"); + err = -ERESTARTSYS; + } + + if ((err == -ERESTARTSYS) && (c->status == Connected) && (!flushed)) { + P9_DPRINTK(P9_DEBUG_9P, "flushing\n"); + spin_lock_irqsave(&c->lock, flags); + if (req->status == REQ_STATUS_SENT) + req->status = REQ_STATUS_FLSH; + spin_unlock_irqrestore(&c->lock, flags); + sigpending = 1; + flushed = 1; + clear_thread_flag(TIF_SIGPENDING); + + if (c->trans_mod->cancel(c, req)) { + err = p9_client_flush(c, req); + if (err == 0) + goto again; + } + } + + if (sigpending) { + spin_lock_irqsave(¤t->sighand->siglock, flags); + recalc_sigpending(); + spin_unlock_irqrestore(¤t->sighand->siglock, flags); + } + + if (err < 0) + goto reterr; + + size = le32_to_cpu(*(__le32 *) rdata); + + err = p9_deserialize_fcall(rdata, size, req->rc, c->dotu); + if (err < 0) { + P9_DPRINTK(P9_DEBUG_9P, + "9p debug: client rpc deserialize returned %d\n", err); + goto reterr; + } + +#ifdef CONFIG_NET_9P_DEBUG + if ((p9_debug_level&P9_DEBUG_FCALL) == P9_DEBUG_FCALL) { + char buf[150]; + + p9_printfcall(buf, sizeof(buf), req->rc, c->dotu); + printk(KERN_NOTICE ">>> %p %s\n", c, buf); + } +#endif + + if (req->rc->id == P9_RERROR) { + int ecode = req->rc->params.rerror.errno; + struct p9_str *ename = &req->rc->params.rerror.error; + + P9_DPRINTK(P9_DEBUG_MUX, "Rerror %.*s\n", ename->len, + ename->str); + + if (c->dotu) + err = -ecode; + + if (!err) { + err = p9_errstr2errno(ename->str, ename->len); + + /* string match failed */ + if (!err) { + PRINT_FCALL_ERROR("unknown error", req->rc); + err = -ESERVERFAULT; + } + } + } else + err = 0; + +reterr: + p9_free_req(c, req); + + P9_DPRINTK(P9_DEBUG_9P, "returning %d\n", err); + return err; +} + static struct p9_fid *p9_fid_create(struct p9_client *clnt) { int err; @@ -339,20 +590,6 @@ static void p9_fid_destroy(struct p9_fid *fid) kfree(fid); } -/** - * p9_client_rpc - sends 9P request and waits until a response is available. - * The function can be interrupted. - * @c: client data - * @tc: request to be sent - * @rc: pointer where a pointer to the response is stored - */ -int -p9_client_rpc(struct p9_client *c, struct p9_fcall *tc, - struct p9_fcall **rc) -{ - return c->trans_mod->rpc(c, tc, rc); -} - struct p9_client *p9_client_create(const char *dev_name, char *options) { int err, n; |