linux/net/9p/client.c
Eric Van Hensbergen fea511a644 9p: move request management to client code
The virtio transport uses a simplified request management system
that I want to use for all transports.  This patch adapts and moves the
exisiting code for managing requests to the client common code.
Later patches will apply these mechanisms to the other transports.

Signed-off-by: Eric Van Hensbergen <ericvh@gmail.com>
2008-10-17 11:04:42 -05:00

1225 lines
23 KiB
C

/*
* 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
*
*/
#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/sched.h>
#include <linux/uaccess.h>
#include <net/9p/9p.h>
#include <linux/parser.h>
#include <net/9p/client.h>
#include <net/9p/transport.h>
/*
* Client Option Parsing (code inspired by NFS code)
* - a little lazy - parse all client options
*/
enum {
Opt_msize,
Opt_trans,
Opt_legacy,
Opt_err,
};
static const match_table_t tokens = {
{Opt_msize, "msize=%u"},
{Opt_legacy, "noextend"},
{Opt_trans, "trans=%s"},
{Opt_err, NULL},
};
/**
* v9fs_parse_options - parse mount options into session structure
* @options: options string passed from mount
* @v9ses: existing v9fs session information
*
* Return 0 upon success, -ERRNO upon failure
*/
static int parse_opts(char *opts, struct p9_client *clnt)
{
char *options;
char *p;
substring_t args[MAX_OPT_ARGS];
int option;
int ret = 0;
clnt->dotu = 1;
clnt->msize = 8192;
if (!opts)
return 0;
options = kstrdup(opts, GFP_KERNEL);
if (!options) {
P9_DPRINTK(P9_DEBUG_ERROR,
"failed to allocate copy of option string\n");
return -ENOMEM;
}
while ((p = strsep(&options, ",")) != NULL) {
int token;
if (!*p)
continue;
token = match_token(p, tokens, args);
if (token < Opt_trans) {
int r = match_int(&args[0], &option);
if (r < 0) {
P9_DPRINTK(P9_DEBUG_ERROR,
"integer field, but no integer?\n");
ret = r;
continue;
}
}
switch (token) {
case Opt_msize:
clnt->msize = option;
break;
case Opt_trans:
clnt->trans_mod = v9fs_get_trans_by_name(&args[0]);
break;
case Opt_legacy:
clnt->dotu = 0;
break;
default:
continue;
}
}
if (!clnt->trans_mod)
clnt->trans_mod = v9fs_get_default_trans();
kfree(options);
return ret;
}
/**
* 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 accomodate 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.
*
*/
struct p9_req_t *p9_tag_alloc(struct p9_client *c, u16 tag)
{
unsigned long flags;
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) {
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]) {
printk(KERN_ERR "Couldn't grow tag array\n");
BUG();
}
for (col = 0; col < P9_ROW_MAXTAG; col++) {
c->reqs[row][col].status = REQ_STATUS_IDLE;
c->reqs[row][col].flush_tag = P9_NOTAG;
c->reqs[row][col].wq = kmalloc(
sizeof(wait_queue_head_t), GFP_ATOMIC);
if (!c->reqs[row][col].wq) {
printk(KERN_ERR
"Couldn't grow tag array\n");
BUG();
}
init_waitqueue_head(c->reqs[row][col].wq);
}
c->max_tag += P9_ROW_MAXTAG;
}
spin_unlock_irqrestore(&c->lock, flags);
}
row = tag / P9_ROW_MAXTAG;
col = tag % P9_ROW_MAXTAG;
c->reqs[row][col].status = REQ_STATUS_ALLOC;
c->reqs[row][col].flush_tag = P9_NOTAG;
return &c->reqs[row][col];
}
EXPORT_SYMBOL(p9_tag_alloc);
/**
* 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++;
BUG_ON(tag >= c->max_tag);
row = tag / P9_ROW_MAXTAG;
col = tag % P9_ROW_MAXTAG;
return &c->reqs[row][col];
}
EXPORT_SYMBOL(p9_tag_lookup);
/**
* p9_tag_init - setup tags structure and contents
* @tags: tags structure from the 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);
c->tagpool = NULL;
goto error;
}
p9_idpool_get(c->tagpool); /* reserve tag 0 */
c->max_tag = 0;
error:
return err;
}
/**
* p9_tag_cleanup - cleans up tags structure and reclaims resources
* @tags: tags structure from the 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_DPRINTK(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_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]);
}
c->max_tag = 0;
}
static struct p9_fid *p9_fid_create(struct p9_client *clnt)
{
int err;
struct p9_fid *fid;
P9_DPRINTK(P9_DEBUG_9P, "clnt %p\n", clnt);
fid = kmalloc(sizeof(struct p9_fid), GFP_KERNEL);
if (!fid)
return ERR_PTR(-ENOMEM);
fid->fid = p9_idpool_get(clnt->fidpool);
if (fid->fid < 0) {
err = -ENOSPC;
goto error;
}
memset(&fid->qid, 0, sizeof(struct p9_qid));
fid->mode = -1;
fid->rdir_fpos = 0;
fid->rdir_pos = 0;
fid->rdir_fcall = NULL;
fid->uid = current->fsuid;
fid->clnt = clnt;
fid->aux = NULL;
spin_lock(&clnt->lock);
list_add(&fid->flist, &clnt->fidlist);
spin_unlock(&clnt->lock);
return fid;
error:
kfree(fid);
return ERR_PTR(err);
}
static void p9_fid_destroy(struct p9_fid *fid)
{
struct p9_client *clnt;
P9_DPRINTK(P9_DEBUG_9P, "fid %d\n", fid->fid);
clnt = fid->clnt;
p9_idpool_put(fid->fid, clnt->fidpool);
spin_lock(&clnt->lock);
list_del(&fid->flist);
spin_unlock(&clnt->lock);
kfree(fid->rdir_fcall);
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;
struct p9_client *clnt;
struct p9_fcall *tc, *rc;
struct p9_str *version;
err = 0;
tc = NULL;
rc = NULL;
clnt = kmalloc(sizeof(struct p9_client), GFP_KERNEL);
if (!clnt)
return ERR_PTR(-ENOMEM);
clnt->trans_mod = NULL;
clnt->trans = NULL;
spin_lock_init(&clnt->lock);
INIT_LIST_HEAD(&clnt->fidlist);
clnt->fidpool = p9_idpool_create();
if (IS_ERR(clnt->fidpool)) {
err = PTR_ERR(clnt->fidpool);
clnt->fidpool = NULL;
goto error;
}
p9_tag_init(clnt);
err = parse_opts(options, clnt);
if (err < 0)
goto error;
if (clnt->trans_mod == NULL) {
err = -EPROTONOSUPPORT;
P9_DPRINTK(P9_DEBUG_ERROR,
"No transport defined or default transport\n");
goto error;
}
P9_DPRINTK(P9_DEBUG_9P, "clnt %p trans %p msize %d dotu %d\n",
clnt, clnt->trans_mod, clnt->msize, clnt->dotu);
err = clnt->trans_mod->create(clnt, dev_name, options);
if (err)
goto error;
if ((clnt->msize+P9_IOHDRSZ) > clnt->trans_mod->maxsize)
clnt->msize = clnt->trans_mod->maxsize-P9_IOHDRSZ;
tc = p9_create_tversion(clnt->msize, clnt->dotu?"9P2000.u":"9P2000");
if (IS_ERR(tc)) {
err = PTR_ERR(tc);
tc = NULL;
goto error;
}
err = p9_client_rpc(clnt, tc, &rc);
if (err)
goto error;
version = &rc->params.rversion.version;
if (version->len == 8 && !memcmp(version->str, "9P2000.u", 8))
clnt->dotu = 1;
else if (version->len == 6 && !memcmp(version->str, "9P2000", 6))
clnt->dotu = 0;
else {
err = -EREMOTEIO;
goto error;
}
n = rc->params.rversion.msize;
if (n < clnt->msize)
clnt->msize = n;
kfree(tc);
kfree(rc);
return clnt;
error:
kfree(tc);
kfree(rc);
p9_client_destroy(clnt);
return ERR_PTR(err);
}
EXPORT_SYMBOL(p9_client_create);
void p9_client_destroy(struct p9_client *clnt)
{
struct p9_fid *fid, *fidptr;
P9_DPRINTK(P9_DEBUG_9P, "clnt %p\n", clnt);
if (clnt->trans_mod)
clnt->trans_mod->close(clnt);
v9fs_put_trans(clnt->trans_mod);
list_for_each_entry_safe(fid, fidptr, &clnt->fidlist, flist)
p9_fid_destroy(fid);
if (clnt->fidpool)
p9_idpool_destroy(clnt->fidpool);
p9_tag_cleanup(clnt);
kfree(clnt);
}
EXPORT_SYMBOL(p9_client_destroy);
void p9_client_disconnect(struct p9_client *clnt)
{
P9_DPRINTK(P9_DEBUG_9P, "clnt %p\n", clnt);
clnt->status = Disconnected;
}
EXPORT_SYMBOL(p9_client_disconnect);
struct p9_fid *p9_client_attach(struct p9_client *clnt, struct p9_fid *afid,
char *uname, u32 n_uname, char *aname)
{
int err;
struct p9_fcall *tc, *rc;
struct p9_fid *fid;
P9_DPRINTK(P9_DEBUG_9P, "clnt %p afid %d uname %s aname %s\n",
clnt, afid?afid->fid:-1, uname, aname);
err = 0;
tc = NULL;
rc = NULL;
fid = p9_fid_create(clnt);
if (IS_ERR(fid)) {
err = PTR_ERR(fid);
fid = NULL;
goto error;
}
tc = p9_create_tattach(fid->fid, afid?afid->fid:P9_NOFID, uname, aname,
n_uname, clnt->dotu);
if (IS_ERR(tc)) {
err = PTR_ERR(tc);
tc = NULL;
goto error;
}
err = p9_client_rpc(clnt, tc, &rc);
if (err)
goto error;
memmove(&fid->qid, &rc->params.rattach.qid, sizeof(struct p9_qid));
kfree(tc);
kfree(rc);
return fid;
error:
kfree(tc);
kfree(rc);
if (fid)
p9_fid_destroy(fid);
return ERR_PTR(err);
}
EXPORT_SYMBOL(p9_client_attach);
struct p9_fid *p9_client_auth(struct p9_client *clnt, char *uname,
u32 n_uname, char *aname)
{
int err;
struct p9_fcall *tc, *rc;
struct p9_fid *fid;
P9_DPRINTK(P9_DEBUG_9P, "clnt %p uname %s aname %s\n", clnt, uname,
aname);
err = 0;
tc = NULL;
rc = NULL;
fid = p9_fid_create(clnt);
if (IS_ERR(fid)) {
err = PTR_ERR(fid);
fid = NULL;
goto error;
}
tc = p9_create_tauth(fid->fid, uname, aname, n_uname, clnt->dotu);
if (IS_ERR(tc)) {
err = PTR_ERR(tc);
tc = NULL;
goto error;
}
err = p9_client_rpc(clnt, tc, &rc);
if (err)
goto error;
memmove(&fid->qid, &rc->params.rauth.qid, sizeof(struct p9_qid));
kfree(tc);
kfree(rc);
return fid;
error:
kfree(tc);
kfree(rc);
if (fid)
p9_fid_destroy(fid);
return ERR_PTR(err);
}
EXPORT_SYMBOL(p9_client_auth);
struct p9_fid *p9_client_walk(struct p9_fid *oldfid, int nwname, char **wnames,
int clone)
{
int err;
struct p9_fcall *tc, *rc;
struct p9_client *clnt;
struct p9_fid *fid;
P9_DPRINTK(P9_DEBUG_9P, "fid %d nwname %d wname[0] %s\n",
oldfid->fid, nwname, wnames?wnames[0]:NULL);
err = 0;
tc = NULL;
rc = NULL;
clnt = oldfid->clnt;
if (clone) {
fid = p9_fid_create(clnt);
if (IS_ERR(fid)) {
err = PTR_ERR(fid);
fid = NULL;
goto error;
}
fid->uid = oldfid->uid;
} else
fid = oldfid;
tc = p9_create_twalk(oldfid->fid, fid->fid, nwname, wnames);
if (IS_ERR(tc)) {
err = PTR_ERR(tc);
tc = NULL;
goto error;
}
err = p9_client_rpc(clnt, tc, &rc);
if (err) {
if (rc && rc->id == P9_RWALK)
goto clunk_fid;
else
goto error;
}
if (rc->params.rwalk.nwqid != nwname) {
err = -ENOENT;
goto clunk_fid;
}
if (nwname)
memmove(&fid->qid,
&rc->params.rwalk.wqids[rc->params.rwalk.nwqid - 1],
sizeof(struct p9_qid));
else
fid->qid = oldfid->qid;
kfree(tc);
kfree(rc);
return fid;
clunk_fid:
kfree(tc);
kfree(rc);
rc = NULL;
tc = p9_create_tclunk(fid->fid);
if (IS_ERR(tc)) {
err = PTR_ERR(tc);
tc = NULL;
goto error;
}
p9_client_rpc(clnt, tc, &rc);
error:
kfree(tc);
kfree(rc);
if (fid && (fid != oldfid))
p9_fid_destroy(fid);
return ERR_PTR(err);
}
EXPORT_SYMBOL(p9_client_walk);
int p9_client_open(struct p9_fid *fid, int mode)
{
int err;
struct p9_fcall *tc, *rc;
struct p9_client *clnt;
P9_DPRINTK(P9_DEBUG_9P, "fid %d mode %d\n", fid->fid, mode);
err = 0;
tc = NULL;
rc = NULL;
clnt = fid->clnt;
if (fid->mode != -1)
return -EINVAL;
tc = p9_create_topen(fid->fid, mode);
if (IS_ERR(tc)) {
err = PTR_ERR(tc);
tc = NULL;
goto done;
}
err = p9_client_rpc(clnt, tc, &rc);
if (err)
goto done;
fid->mode = mode;
fid->iounit = rc->params.ropen.iounit;
done:
kfree(tc);
kfree(rc);
return err;
}
EXPORT_SYMBOL(p9_client_open);
int p9_client_fcreate(struct p9_fid *fid, char *name, u32 perm, int mode,
char *extension)
{
int err;
struct p9_fcall *tc, *rc;
struct p9_client *clnt;
P9_DPRINTK(P9_DEBUG_9P, "fid %d name %s perm %d mode %d\n", fid->fid,
name, perm, mode);
err = 0;
tc = NULL;
rc = NULL;
clnt = fid->clnt;
if (fid->mode != -1)
return -EINVAL;
tc = p9_create_tcreate(fid->fid, name, perm, mode, extension,
clnt->dotu);
if (IS_ERR(tc)) {
err = PTR_ERR(tc);
tc = NULL;
goto done;
}
err = p9_client_rpc(clnt, tc, &rc);
if (err)
goto done;
fid->mode = mode;
fid->iounit = rc->params.ropen.iounit;
done:
kfree(tc);
kfree(rc);
return err;
}
EXPORT_SYMBOL(p9_client_fcreate);
int p9_client_clunk(struct p9_fid *fid)
{
int err;
struct p9_fcall *tc, *rc;
struct p9_client *clnt;
P9_DPRINTK(P9_DEBUG_9P, "fid %d\n", fid->fid);
err = 0;
tc = NULL;
rc = NULL;
clnt = fid->clnt;
tc = p9_create_tclunk(fid->fid);
if (IS_ERR(tc)) {
err = PTR_ERR(tc);
tc = NULL;
goto done;
}
err = p9_client_rpc(clnt, tc, &rc);
if (err)
goto done;
p9_fid_destroy(fid);
done:
kfree(tc);
kfree(rc);
return err;
}
EXPORT_SYMBOL(p9_client_clunk);
int p9_client_remove(struct p9_fid *fid)
{
int err;
struct p9_fcall *tc, *rc;
struct p9_client *clnt;
P9_DPRINTK(P9_DEBUG_9P, "fid %d\n", fid->fid);
err = 0;
tc = NULL;
rc = NULL;
clnt = fid->clnt;
tc = p9_create_tremove(fid->fid);
if (IS_ERR(tc)) {
err = PTR_ERR(tc);
tc = NULL;
goto done;
}
err = p9_client_rpc(clnt, tc, &rc);
if (err)
goto done;
p9_fid_destroy(fid);
done:
kfree(tc);
kfree(rc);
return err;
}
EXPORT_SYMBOL(p9_client_remove);
int p9_client_read(struct p9_fid *fid, char *data, u64 offset, u32 count)
{
int err, n, rsize, total;
struct p9_fcall *tc, *rc;
struct p9_client *clnt;
P9_DPRINTK(P9_DEBUG_9P, "fid %d offset %llu %d\n", fid->fid,
(long long unsigned) offset, count);
err = 0;
tc = NULL;
rc = NULL;
clnt = fid->clnt;
total = 0;
rsize = fid->iounit;
if (!rsize || rsize > clnt->msize-P9_IOHDRSZ)
rsize = clnt->msize - P9_IOHDRSZ;
do {
if (count < rsize)
rsize = count;
tc = p9_create_tread(fid->fid, offset, rsize);
if (IS_ERR(tc)) {
err = PTR_ERR(tc);
tc = NULL;
goto error;
}
err = p9_client_rpc(clnt, tc, &rc);
if (err)
goto error;
n = rc->params.rread.count;
if (n > count)
n = count;
memmove(data, rc->params.rread.data, n);
count -= n;
data += n;
offset += n;
total += n;
kfree(tc);
tc = NULL;
kfree(rc);
rc = NULL;
} while (count > 0 && n == rsize);
return total;
error:
kfree(tc);
kfree(rc);
return err;
}
EXPORT_SYMBOL(p9_client_read);
int p9_client_write(struct p9_fid *fid, char *data, u64 offset, u32 count)
{
int err, n, rsize, total;
struct p9_fcall *tc, *rc;
struct p9_client *clnt;
P9_DPRINTK(P9_DEBUG_9P, "fid %d offset %llu count %d\n", fid->fid,
(long long unsigned) offset, count);
err = 0;
tc = NULL;
rc = NULL;
clnt = fid->clnt;
total = 0;
rsize = fid->iounit;
if (!rsize || rsize > clnt->msize-P9_IOHDRSZ)
rsize = clnt->msize - P9_IOHDRSZ;
do {
if (count < rsize)
rsize = count;
tc = p9_create_twrite(fid->fid, offset, rsize, data);
if (IS_ERR(tc)) {
err = PTR_ERR(tc);
tc = NULL;
goto error;
}
err = p9_client_rpc(clnt, tc, &rc);
if (err)
goto error;
n = rc->params.rread.count;
count -= n;
data += n;
offset += n;
total += n;
kfree(tc);
tc = NULL;
kfree(rc);
rc = NULL;
} while (count > 0);
return total;
error:
kfree(tc);
kfree(rc);
return err;
}
EXPORT_SYMBOL(p9_client_write);
int
p9_client_uread(struct p9_fid *fid, char __user *data, u64 offset, u32 count)
{
int err, n, rsize, total;
struct p9_fcall *tc, *rc;
struct p9_client *clnt;
P9_DPRINTK(P9_DEBUG_9P, "fid %d offset %llu count %d\n", fid->fid,
(long long unsigned) offset, count);
err = 0;
tc = NULL;
rc = NULL;
clnt = fid->clnt;
total = 0;
rsize = fid->iounit;
if (!rsize || rsize > clnt->msize-P9_IOHDRSZ)
rsize = clnt->msize - P9_IOHDRSZ;
do {
if (count < rsize)
rsize = count;
tc = p9_create_tread(fid->fid, offset, rsize);
if (IS_ERR(tc)) {
err = PTR_ERR(tc);
tc = NULL;
goto error;
}
err = p9_client_rpc(clnt, tc, &rc);
if (err)
goto error;
n = rc->params.rread.count;
if (n > count)
n = count;
err = copy_to_user(data, rc->params.rread.data, n);
if (err) {
err = -EFAULT;
goto error;
}
count -= n;
data += n;
offset += n;
total += n;
kfree(tc);
tc = NULL;
kfree(rc);
rc = NULL;
} while (count > 0 && n == rsize);
return total;
error:
kfree(tc);
kfree(rc);
return err;
}
EXPORT_SYMBOL(p9_client_uread);
int
p9_client_uwrite(struct p9_fid *fid, const char __user *data, u64 offset,
u32 count)
{
int err, n, rsize, total;
struct p9_fcall *tc, *rc;
struct p9_client *clnt;
P9_DPRINTK(P9_DEBUG_9P, "fid %d offset %llu count %d\n", fid->fid,
(long long unsigned) offset, count);
err = 0;
tc = NULL;
rc = NULL;
clnt = fid->clnt;
total = 0;
rsize = fid->iounit;
if (!rsize || rsize > clnt->msize-P9_IOHDRSZ)
rsize = clnt->msize - P9_IOHDRSZ;
do {
if (count < rsize)
rsize = count;
tc = p9_create_twrite_u(fid->fid, offset, rsize, data);
if (IS_ERR(tc)) {
err = PTR_ERR(tc);
tc = NULL;
goto error;
}
err = p9_client_rpc(clnt, tc, &rc);
if (err)
goto error;
n = rc->params.rread.count;
count -= n;
data += n;
offset += n;
total += n;
kfree(tc);
tc = NULL;
kfree(rc);
rc = NULL;
} while (count > 0);
return total;
error:
kfree(tc);
kfree(rc);
return err;
}
EXPORT_SYMBOL(p9_client_uwrite);
int p9_client_readn(struct p9_fid *fid, char *data, u64 offset, u32 count)
{
int n, total;
P9_DPRINTK(P9_DEBUG_9P, "fid %d offset %llu count %d\n", fid->fid,
(long long unsigned) offset, count);
n = 0;
total = 0;
while (count) {
n = p9_client_read(fid, data, offset, count);
if (n <= 0)
break;
data += n;
offset += n;
count -= n;
total += n;
}
if (n < 0)
total = n;
return total;
}
EXPORT_SYMBOL(p9_client_readn);
static struct p9_stat *p9_clone_stat(struct p9_stat *st, int dotu)
{
int n;
char *p;
struct p9_stat *ret;
n = sizeof(struct p9_stat) + st->name.len + st->uid.len + st->gid.len +
st->muid.len;
if (dotu)
n += st->extension.len;
ret = kmalloc(n, GFP_KERNEL);
if (!ret)
return ERR_PTR(-ENOMEM);
memmove(ret, st, sizeof(struct p9_stat));
p = ((char *) ret) + sizeof(struct p9_stat);
memmove(p, st->name.str, st->name.len);
ret->name.str = p;
p += st->name.len;
memmove(p, st->uid.str, st->uid.len);
ret->uid.str = p;
p += st->uid.len;
memmove(p, st->gid.str, st->gid.len);
ret->gid.str = p;
p += st->gid.len;
memmove(p, st->muid.str, st->muid.len);
ret->muid.str = p;
p += st->muid.len;
if (dotu) {
memmove(p, st->extension.str, st->extension.len);
ret->extension.str = p;
p += st->extension.len;
}
return ret;
}
struct p9_stat *p9_client_stat(struct p9_fid *fid)
{
int err;
struct p9_fcall *tc, *rc;
struct p9_client *clnt;
struct p9_stat *ret;
P9_DPRINTK(P9_DEBUG_9P, "fid %d\n", fid->fid);
err = 0;
tc = NULL;
rc = NULL;
ret = NULL;
clnt = fid->clnt;
tc = p9_create_tstat(fid->fid);
if (IS_ERR(tc)) {
err = PTR_ERR(tc);
tc = NULL;
goto error;
}
err = p9_client_rpc(clnt, tc, &rc);
if (err)
goto error;
ret = p9_clone_stat(&rc->params.rstat.stat, clnt->dotu);
if (IS_ERR(ret)) {
err = PTR_ERR(ret);
ret = NULL;
goto error;
}
kfree(tc);
kfree(rc);
return ret;
error:
kfree(tc);
kfree(rc);
kfree(ret);
return ERR_PTR(err);
}
EXPORT_SYMBOL(p9_client_stat);
int p9_client_wstat(struct p9_fid *fid, struct p9_wstat *wst)
{
int err;
struct p9_fcall *tc, *rc;
struct p9_client *clnt;
P9_DPRINTK(P9_DEBUG_9P, "fid %d\n", fid->fid);
err = 0;
tc = NULL;
rc = NULL;
clnt = fid->clnt;
tc = p9_create_twstat(fid->fid, wst, clnt->dotu);
if (IS_ERR(tc)) {
err = PTR_ERR(tc);
tc = NULL;
goto done;
}
err = p9_client_rpc(clnt, tc, &rc);
done:
kfree(tc);
kfree(rc);
return err;
}
EXPORT_SYMBOL(p9_client_wstat);
struct p9_stat *p9_client_dirread(struct p9_fid *fid, u64 offset)
{
int err, n, m;
struct p9_fcall *tc, *rc;
struct p9_client *clnt;
struct p9_stat st, *ret;
P9_DPRINTK(P9_DEBUG_9P, "fid %d offset %llu\n", fid->fid,
(long long unsigned) offset);
err = 0;
tc = NULL;
rc = NULL;
ret = NULL;
clnt = fid->clnt;
/* if the offset is below or above the current response, free it */
if (offset < fid->rdir_fpos || (fid->rdir_fcall &&
offset >= fid->rdir_fpos+fid->rdir_fcall->params.rread.count)) {
fid->rdir_pos = 0;
if (fid->rdir_fcall)
fid->rdir_fpos += fid->rdir_fcall->params.rread.count;
kfree(fid->rdir_fcall);
fid->rdir_fcall = NULL;
if (offset < fid->rdir_fpos)
fid->rdir_fpos = 0;
}
if (!fid->rdir_fcall) {
n = fid->iounit;
if (!n || n > clnt->msize-P9_IOHDRSZ)
n = clnt->msize - P9_IOHDRSZ;
while (1) {
if (fid->rdir_fcall) {
fid->rdir_fpos +=
fid->rdir_fcall->params.rread.count;
kfree(fid->rdir_fcall);
fid->rdir_fcall = NULL;
}
tc = p9_create_tread(fid->fid, fid->rdir_fpos, n);
if (IS_ERR(tc)) {
err = PTR_ERR(tc);
tc = NULL;
goto error;
}
err = p9_client_rpc(clnt, tc, &rc);
if (err)
goto error;
n = rc->params.rread.count;
if (n == 0)
goto done;
fid->rdir_fcall = rc;
rc = NULL;
if (offset >= fid->rdir_fpos &&
offset < fid->rdir_fpos+n)
break;
}
fid->rdir_pos = 0;
}
m = offset - fid->rdir_fpos;
if (m < 0)
goto done;
n = p9_deserialize_stat(fid->rdir_fcall->params.rread.data + m,
fid->rdir_fcall->params.rread.count - m, &st, clnt->dotu);
if (!n) {
err = -EIO;
goto error;
}
fid->rdir_pos += n;
st.size = n;
ret = p9_clone_stat(&st, clnt->dotu);
if (IS_ERR(ret)) {
err = PTR_ERR(ret);
ret = NULL;
goto error;
}
done:
kfree(tc);
kfree(rc);
return ret;
error:
kfree(tc);
kfree(rc);
kfree(ret);
return ERR_PTR(err);
}
EXPORT_SYMBOL(p9_client_dirread);