2007-07-11 06:57:28 +08:00
|
|
|
/*
|
|
|
|
* linux/fs/9p/trans_fd.c
|
|
|
|
*
|
|
|
|
* Fd transport layer. Includes deprecated socket layer.
|
|
|
|
*
|
|
|
|
* Copyright (C) 2006 by Russ Cox <rsc@swtch.com>
|
|
|
|
* Copyright (C) 2004-2005 by Latchesar Ionkov <lucho@ionkov.net>
|
2008-02-07 09:25:03 +08:00
|
|
|
* Copyright (C) 2004-2008 by Eric Van Hensbergen <ericvh@gmail.com>
|
2007-07-11 06:57:28 +08:00
|
|
|
* Copyright (C) 1997-2002 by Ron Minnich <rminnich@sarnoff.com>
|
|
|
|
*
|
|
|
|
* 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/in.h>
|
|
|
|
#include <linux/module.h>
|
|
|
|
#include <linux/net.h>
|
|
|
|
#include <linux/ipv6.h>
|
2008-02-07 09:25:03 +08:00
|
|
|
#include <linux/kthread.h>
|
2007-07-11 06:57:28 +08:00
|
|
|
#include <linux/errno.h>
|
|
|
|
#include <linux/kernel.h>
|
|
|
|
#include <linux/un.h>
|
|
|
|
#include <linux/uaccess.h>
|
|
|
|
#include <linux/inet.h>
|
|
|
|
#include <linux/idr.h>
|
|
|
|
#include <linux/file.h>
|
2007-10-18 03:31:07 +08:00
|
|
|
#include <linux/parser.h>
|
2007-07-11 06:57:28 +08:00
|
|
|
#include <net/9p/9p.h>
|
2008-10-14 07:45:25 +08:00
|
|
|
#include <net/9p/client.h>
|
2007-07-11 06:57:28 +08:00
|
|
|
#include <net/9p/transport.h>
|
|
|
|
|
|
|
|
#define P9_PORT 564
|
2007-10-18 03:31:07 +08:00
|
|
|
#define MAX_SOCK_BUF (64*1024)
|
2008-02-07 09:25:03 +08:00
|
|
|
#define MAXPOLLWADDR 2
|
2007-10-18 03:31:07 +08:00
|
|
|
|
2008-03-05 21:08:09 +08:00
|
|
|
/**
|
|
|
|
* struct p9_fd_opts - per-transport options
|
|
|
|
* @rfd: file descriptor for reading (trans=fd)
|
|
|
|
* @wfd: file descriptor for writing (trans=fd)
|
|
|
|
* @port: port to connect to (trans=tcp)
|
|
|
|
*
|
|
|
|
*/
|
|
|
|
|
2007-10-18 03:31:07 +08:00
|
|
|
struct p9_fd_opts {
|
|
|
|
int rfd;
|
|
|
|
int wfd;
|
|
|
|
u16 port;
|
|
|
|
};
|
2007-07-11 06:57:28 +08:00
|
|
|
|
2008-03-05 21:08:09 +08:00
|
|
|
/**
|
|
|
|
* struct p9_trans_fd - transport state
|
|
|
|
* @rd: reference to file to read from
|
|
|
|
* @wr: reference of file to write to
|
|
|
|
* @conn: connection state reference
|
|
|
|
*
|
|
|
|
*/
|
|
|
|
|
2007-07-11 06:57:28 +08:00
|
|
|
struct p9_trans_fd {
|
|
|
|
struct file *rd;
|
|
|
|
struct file *wr;
|
2008-02-07 09:25:03 +08:00
|
|
|
struct p9_conn *conn;
|
2007-07-11 06:57:28 +08:00
|
|
|
};
|
|
|
|
|
2007-10-18 03:31:07 +08:00
|
|
|
/*
|
|
|
|
* Option Parsing (code inspired by NFS code)
|
|
|
|
* - a little lazy - parse all fd-transport options
|
|
|
|
*/
|
2007-07-11 06:57:28 +08:00
|
|
|
|
2007-10-18 03:31:07 +08:00
|
|
|
enum {
|
|
|
|
/* Options that take integer arguments */
|
2007-11-06 22:02:53 +08:00
|
|
|
Opt_port, Opt_rfdno, Opt_wfdno, Opt_err,
|
2007-10-18 03:31:07 +08:00
|
|
|
};
|
2007-07-11 06:57:28 +08:00
|
|
|
|
2008-10-13 17:46:57 +08:00
|
|
|
static const match_table_t tokens = {
|
2007-10-18 03:31:07 +08:00
|
|
|
{Opt_port, "port=%u"},
|
|
|
|
{Opt_rfdno, "rfdno=%u"},
|
|
|
|
{Opt_wfdno, "wfdno=%u"},
|
2007-11-06 22:02:53 +08:00
|
|
|
{Opt_err, NULL},
|
2007-10-18 03:31:07 +08:00
|
|
|
};
|
2007-07-11 06:57:28 +08:00
|
|
|
|
2008-02-07 09:25:03 +08:00
|
|
|
enum {
|
|
|
|
Rworksched = 1, /* read work scheduled or running */
|
|
|
|
Rpending = 2, /* can read */
|
|
|
|
Wworksched = 4, /* write work scheduled or running */
|
|
|
|
Wpending = 8, /* can write */
|
|
|
|
};
|
|
|
|
|
2008-10-14 07:45:25 +08:00
|
|
|
struct p9_poll_wait {
|
|
|
|
struct p9_conn *conn;
|
|
|
|
wait_queue_t wait;
|
|
|
|
wait_queue_head_t *wait_addr;
|
2008-03-05 21:08:09 +08:00
|
|
|
};
|
|
|
|
|
|
|
|
/**
|
|
|
|
* struct p9_conn - fd mux connection state information
|
|
|
|
* @mux_list: list link for mux to manage multiple connections (?)
|
2008-10-14 07:45:25 +08:00
|
|
|
* @client: reference to client instance for this connection
|
2008-03-05 21:08:09 +08:00
|
|
|
* @err: error state
|
|
|
|
* @req_list: accounting for requests which have been sent
|
|
|
|
* @unsent_req_list: accounting for requests that haven't been sent
|
2008-10-14 07:45:22 +08:00
|
|
|
* @req: current request being processed (if any)
|
|
|
|
* @tmp_buf: temporary buffer to read in header
|
|
|
|
* @rsize: amount to read for current frame
|
2008-03-05 21:08:09 +08:00
|
|
|
* @rpos: read position in current frame
|
|
|
|
* @rbuf: current read buffer
|
|
|
|
* @wpos: write position for current frame
|
|
|
|
* @wsize: amount of data to write for current frame
|
|
|
|
* @wbuf: current write buffer
|
|
|
|
* @poll_wait: array of wait_q's for various worker threads
|
|
|
|
* @poll_waddr: ????
|
|
|
|
* @pt: poll state
|
|
|
|
* @rq: current read work
|
|
|
|
* @wq: current write work
|
|
|
|
* @wsched: ????
|
|
|
|
*
|
|
|
|
*/
|
2008-02-07 09:25:03 +08:00
|
|
|
|
|
|
|
struct p9_conn {
|
|
|
|
struct list_head mux_list;
|
2008-10-14 07:45:25 +08:00
|
|
|
struct p9_client *client;
|
2008-02-07 09:25:03 +08:00
|
|
|
int err;
|
|
|
|
struct list_head req_list;
|
|
|
|
struct list_head unsent_req_list;
|
2008-10-14 07:45:22 +08:00
|
|
|
struct p9_req_t *req;
|
|
|
|
char tmp_buf[7];
|
|
|
|
int rsize;
|
2008-02-07 09:25:03 +08:00
|
|
|
int rpos;
|
|
|
|
char *rbuf;
|
|
|
|
int wpos;
|
|
|
|
int wsize;
|
|
|
|
char *wbuf;
|
2008-10-14 07:45:25 +08:00
|
|
|
struct list_head poll_pending_link;
|
|
|
|
struct p9_poll_wait poll_wait[MAXPOLLWADDR];
|
2008-02-07 09:25:03 +08:00
|
|
|
poll_table pt;
|
|
|
|
struct work_struct rq;
|
|
|
|
struct work_struct wq;
|
|
|
|
unsigned long wsched;
|
|
|
|
};
|
|
|
|
|
2008-10-14 07:45:25 +08:00
|
|
|
static DEFINE_SPINLOCK(p9_poll_lock);
|
|
|
|
static LIST_HEAD(p9_poll_pending_list);
|
2008-02-07 09:25:03 +08:00
|
|
|
static struct workqueue_struct *p9_mux_wq;
|
2008-10-14 07:45:25 +08:00
|
|
|
static struct task_struct *p9_poll_task;
|
2008-02-07 09:25:03 +08:00
|
|
|
|
2008-10-14 07:45:25 +08:00
|
|
|
static void p9_mux_poll_stop(struct p9_conn *m)
|
2008-02-07 09:25:03 +08:00
|
|
|
{
|
2008-10-14 07:45:25 +08:00
|
|
|
unsigned long flags;
|
|
|
|
int i;
|
2008-02-07 09:25:03 +08:00
|
|
|
|
2008-10-14 07:45:25 +08:00
|
|
|
for (i = 0; i < ARRAY_SIZE(m->poll_wait); i++) {
|
|
|
|
struct p9_poll_wait *pwait = &m->poll_wait[i];
|
2008-02-07 09:25:03 +08:00
|
|
|
|
2008-10-14 07:45:25 +08:00
|
|
|
if (pwait->wait_addr) {
|
|
|
|
remove_wait_queue(pwait->wait_addr, &pwait->wait);
|
|
|
|
pwait->wait_addr = NULL;
|
2008-02-07 09:25:03 +08:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2008-10-14 07:45:25 +08:00
|
|
|
spin_lock_irqsave(&p9_poll_lock, flags);
|
|
|
|
list_del_init(&m->poll_pending_link);
|
|
|
|
spin_unlock_irqrestore(&p9_poll_lock, flags);
|
2008-02-07 09:25:03 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
2008-10-14 07:45:24 +08:00
|
|
|
* p9_conn_cancel - cancel all pending requests with error
|
|
|
|
* @m: mux data
|
|
|
|
* @err: error code
|
2008-02-07 09:25:03 +08:00
|
|
|
*
|
|
|
|
*/
|
2008-03-05 21:08:09 +08:00
|
|
|
|
2008-10-14 07:45:24 +08:00
|
|
|
void p9_conn_cancel(struct p9_conn *m, int err)
|
2008-02-07 09:25:03 +08:00
|
|
|
{
|
2008-10-14 07:45:22 +08:00
|
|
|
struct p9_req_t *req, *rtmp;
|
2008-10-14 07:45:21 +08:00
|
|
|
unsigned long flags;
|
2008-10-14 07:45:24 +08:00
|
|
|
LIST_HEAD(cancel_list);
|
2008-02-07 09:25:03 +08:00
|
|
|
|
2008-10-14 07:45:24 +08:00
|
|
|
P9_DPRINTK(P9_DEBUG_ERROR, "mux %p err %d\n", m, err);
|
|
|
|
m->err = err;
|
2008-10-14 07:45:21 +08:00
|
|
|
spin_lock_irqsave(&m->client->lock, flags);
|
2008-10-14 07:45:24 +08:00
|
|
|
list_for_each_entry_safe(req, rtmp, &m->req_list, req_list) {
|
2008-10-14 07:45:22 +08:00
|
|
|
req->status = REQ_STATUS_ERROR;
|
|
|
|
if (!req->t_err)
|
|
|
|
req->t_err = err;
|
2008-10-14 07:45:24 +08:00
|
|
|
list_move(&req->req_list, &cancel_list);
|
|
|
|
}
|
|
|
|
list_for_each_entry_safe(req, rtmp, &m->unsent_req_list, req_list) {
|
2008-10-14 07:45:22 +08:00
|
|
|
req->status = REQ_STATUS_ERROR;
|
|
|
|
if (!req->t_err)
|
|
|
|
req->t_err = err;
|
2008-10-14 07:45:24 +08:00
|
|
|
list_move(&req->req_list, &cancel_list);
|
2008-02-07 09:25:03 +08:00
|
|
|
}
|
2008-10-14 07:45:21 +08:00
|
|
|
spin_unlock_irqrestore(&m->client->lock, flags);
|
2008-02-07 09:25:03 +08:00
|
|
|
|
2008-10-14 07:45:24 +08:00
|
|
|
list_for_each_entry_safe(req, rtmp, &cancel_list, req_list) {
|
|
|
|
list_del(&req->req_list);
|
2008-10-14 07:45:21 +08:00
|
|
|
P9_DPRINTK(P9_DEBUG_ERROR, "call back req %p\n", req);
|
|
|
|
p9_client_cb(m->client, req);
|
2008-02-07 09:25:03 +08:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2008-10-14 07:45:24 +08:00
|
|
|
static unsigned int
|
|
|
|
p9_fd_poll(struct p9_client *client, struct poll_table_struct *pt)
|
2008-02-07 09:25:03 +08:00
|
|
|
{
|
2008-10-14 07:45:24 +08:00
|
|
|
int ret, n;
|
|
|
|
struct p9_trans_fd *ts = NULL;
|
2008-02-07 09:25:03 +08:00
|
|
|
|
2008-10-14 07:45:24 +08:00
|
|
|
if (client && client->status == Connected)
|
|
|
|
ts = client->trans;
|
2008-09-25 05:22:23 +08:00
|
|
|
|
2008-10-14 07:45:24 +08:00
|
|
|
if (!ts)
|
|
|
|
return -EREMOTEIO;
|
2008-09-25 05:22:23 +08:00
|
|
|
|
2008-10-14 07:45:24 +08:00
|
|
|
if (!ts->rd->f_op || !ts->rd->f_op->poll)
|
|
|
|
return -EIO;
|
2008-02-07 09:25:03 +08:00
|
|
|
|
2008-10-14 07:45:24 +08:00
|
|
|
if (!ts->wr->f_op || !ts->wr->f_op->poll)
|
|
|
|
return -EIO;
|
2008-10-14 07:45:25 +08:00
|
|
|
|
2008-10-14 07:45:24 +08:00
|
|
|
ret = ts->rd->f_op->poll(ts->rd, pt);
|
|
|
|
if (ret < 0)
|
|
|
|
return ret;
|
2008-10-14 07:45:25 +08:00
|
|
|
|
2008-10-14 07:45:24 +08:00
|
|
|
if (ts->rd != ts->wr) {
|
|
|
|
n = ts->wr->f_op->poll(ts->wr, pt);
|
|
|
|
if (n < 0)
|
|
|
|
return n;
|
|
|
|
ret = (ret & ~POLLOUT) | (n & ~POLLIN);
|
|
|
|
}
|
|
|
|
|
|
|
|
return ret;
|
2008-10-14 07:45:25 +08:00
|
|
|
}
|
|
|
|
|
2008-02-07 09:25:03 +08:00
|
|
|
/**
|
2008-10-14 07:45:24 +08:00
|
|
|
* p9_fd_read- read from a fd
|
|
|
|
* @client: client instance
|
|
|
|
* @v: buffer to receive data into
|
|
|
|
* @len: size of receive buffer
|
2008-03-05 21:08:09 +08:00
|
|
|
*
|
2008-02-07 09:25:03 +08:00
|
|
|
*/
|
2008-03-05 21:08:09 +08:00
|
|
|
|
2008-10-14 07:45:24 +08:00
|
|
|
static int p9_fd_read(struct p9_client *client, void *v, int len)
|
2008-02-07 09:25:03 +08:00
|
|
|
{
|
2008-10-14 07:45:24 +08:00
|
|
|
int ret;
|
|
|
|
struct p9_trans_fd *ts = NULL;
|
2008-02-07 09:25:03 +08:00
|
|
|
|
2008-10-14 07:45:24 +08:00
|
|
|
if (client && client->status != Disconnected)
|
|
|
|
ts = client->trans;
|
2008-02-07 09:25:03 +08:00
|
|
|
|
2008-10-14 07:45:24 +08:00
|
|
|
if (!ts)
|
|
|
|
return -EREMOTEIO;
|
2008-02-07 09:25:03 +08:00
|
|
|
|
2008-10-14 07:45:24 +08:00
|
|
|
if (!(ts->rd->f_flags & O_NONBLOCK))
|
|
|
|
P9_DPRINTK(P9_DEBUG_ERROR, "blocking read ...\n");
|
2008-02-07 09:25:03 +08:00
|
|
|
|
2008-10-14 07:45:24 +08:00
|
|
|
ret = kernel_read(ts->rd, ts->rd->f_pos, v, len);
|
|
|
|
if (ret <= 0 && ret != -ERESTARTSYS && ret != -EAGAIN)
|
|
|
|
client->status = Disconnected;
|
|
|
|
return ret;
|
2008-02-07 09:25:03 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
2008-10-14 07:45:24 +08:00
|
|
|
* p9_read_work - called when there is some data to be read from a transport
|
|
|
|
* @work: container of work to be done
|
2008-03-05 21:08:09 +08:00
|
|
|
*
|
2008-02-07 09:25:03 +08:00
|
|
|
*/
|
2008-03-05 21:08:09 +08:00
|
|
|
|
2008-10-14 07:45:24 +08:00
|
|
|
static void p9_read_work(struct work_struct *work)
|
2008-02-07 09:25:03 +08:00
|
|
|
{
|
2008-10-14 07:45:24 +08:00
|
|
|
int n, err;
|
|
|
|
struct p9_conn *m;
|
|
|
|
|
|
|
|
m = container_of(work, struct p9_conn, rq);
|
2008-02-07 09:25:03 +08:00
|
|
|
|
|
|
|
if (m->err < 0)
|
|
|
|
return;
|
|
|
|
|
2008-10-14 07:45:24 +08:00
|
|
|
P9_DPRINTK(P9_DEBUG_MUX, "start mux %p pos %d\n", m, m->rpos);
|
2008-02-07 09:25:03 +08:00
|
|
|
|
2008-10-14 07:45:22 +08:00
|
|
|
if (!m->rbuf) {
|
|
|
|
m->rbuf = m->tmp_buf;
|
2008-10-14 07:45:24 +08:00
|
|
|
m->rpos = 0;
|
2008-10-14 07:45:22 +08:00
|
|
|
m->rsize = 7; /* start by reading header */
|
2008-02-07 09:25:03 +08:00
|
|
|
}
|
|
|
|
|
2008-10-14 07:45:24 +08:00
|
|
|
clear_bit(Rpending, &m->wsched);
|
2008-10-14 07:45:22 +08:00
|
|
|
P9_DPRINTK(P9_DEBUG_MUX, "read mux %p pos %d size: %d = %d\n", m,
|
|
|
|
m->rpos, m->rsize, m->rsize-m->rpos);
|
2008-10-14 07:45:24 +08:00
|
|
|
err = p9_fd_read(m->client, m->rbuf + m->rpos,
|
2008-10-14 07:45:22 +08:00
|
|
|
m->rsize - m->rpos);
|
2008-10-14 07:45:24 +08:00
|
|
|
P9_DPRINTK(P9_DEBUG_MUX, "mux %p got %d bytes\n", m, err);
|
|
|
|
if (err == -EAGAIN) {
|
|
|
|
clear_bit(Rworksched, &m->wsched);
|
|
|
|
return;
|
2008-02-07 09:25:03 +08:00
|
|
|
}
|
|
|
|
|
2008-10-14 07:45:24 +08:00
|
|
|
if (err <= 0)
|
|
|
|
goto error;
|
|
|
|
|
|
|
|
m->rpos += err;
|
2008-10-14 07:45:22 +08:00
|
|
|
|
|
|
|
if ((!m->req) && (m->rpos == m->rsize)) { /* header read in */
|
|
|
|
u16 tag;
|
|
|
|
P9_DPRINTK(P9_DEBUG_MUX, "got new header\n");
|
|
|
|
|
|
|
|
n = le32_to_cpu(*(__le32 *) m->rbuf); /* read packet size */
|
2008-10-14 07:45:24 +08:00
|
|
|
if (n >= m->client->msize) {
|
|
|
|
P9_DPRINTK(P9_DEBUG_ERROR,
|
|
|
|
"requested packet size too big: %d\n", n);
|
|
|
|
err = -EIO;
|
|
|
|
goto error;
|
|
|
|
}
|
|
|
|
|
2008-10-14 07:45:22 +08:00
|
|
|
tag = le16_to_cpu(*(__le16 *) (m->rbuf+5)); /* read tag */
|
|
|
|
P9_DPRINTK(P9_DEBUG_MUX, "mux %p pkt: size: %d bytes tag: %d\n",
|
|
|
|
m, n, tag);
|
|
|
|
|
|
|
|
m->req = p9_tag_lookup(m->client, tag);
|
|
|
|
if (!m->req) {
|
|
|
|
P9_DPRINTK(P9_DEBUG_ERROR, "Unexpected packet tag %d\n",
|
|
|
|
tag);
|
|
|
|
err = -EIO;
|
|
|
|
goto error;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (m->req->rc == NULL) {
|
|
|
|
m->req->rc = kmalloc(sizeof(struct p9_fcall) +
|
|
|
|
m->client->msize, GFP_KERNEL);
|
|
|
|
if (!m->req->rc) {
|
|
|
|
m->req = NULL;
|
|
|
|
err = -ENOMEM;
|
|
|
|
goto error;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
m->rbuf = (char *)m->req->rc + sizeof(struct p9_fcall);
|
|
|
|
memcpy(m->rbuf, m->tmp_buf, m->rsize);
|
|
|
|
m->rsize = n;
|
|
|
|
}
|
2008-10-14 07:45:24 +08:00
|
|
|
|
2008-10-14 07:45:22 +08:00
|
|
|
/* not an else because some packets (like clunk) have no payload */
|
|
|
|
if ((m->req) && (m->rpos == m->rsize)) { /* packet is read in */
|
|
|
|
P9_DPRINTK(P9_DEBUG_MUX, "got new packet\n");
|
2008-10-14 07:45:24 +08:00
|
|
|
|
2008-10-14 07:45:21 +08:00
|
|
|
list_del(&m->req->req_list);
|
|
|
|
p9_client_cb(m->client, m->req);
|
2008-10-14 07:45:24 +08:00
|
|
|
|
2008-10-14 07:45:22 +08:00
|
|
|
m->rbuf = NULL;
|
|
|
|
m->rpos = 0;
|
|
|
|
m->rsize = 0;
|
|
|
|
m->req = NULL;
|
2008-10-14 07:45:24 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
if (!list_empty(&m->req_list)) {
|
|
|
|
if (test_and_clear_bit(Rpending, &m->wsched))
|
|
|
|
n = POLLIN;
|
|
|
|
else
|
|
|
|
n = p9_fd_poll(m->client, NULL);
|
|
|
|
|
|
|
|
if (n & POLLIN) {
|
|
|
|
P9_DPRINTK(P9_DEBUG_MUX, "schedule read work %p\n", m);
|
|
|
|
queue_work(p9_mux_wq, &m->rq);
|
|
|
|
} else
|
|
|
|
clear_bit(Rworksched, &m->wsched);
|
|
|
|
} else
|
|
|
|
clear_bit(Rworksched, &m->wsched);
|
|
|
|
|
|
|
|
return;
|
|
|
|
error:
|
|
|
|
p9_conn_cancel(m, err);
|
|
|
|
clear_bit(Rworksched, &m->wsched);
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* p9_fd_write - write to a socket
|
|
|
|
* @client: client instance
|
|
|
|
* @v: buffer to send data from
|
|
|
|
* @len: size of send buffer
|
2008-03-05 21:08:09 +08:00
|
|
|
*
|
2008-02-07 09:25:03 +08:00
|
|
|
*/
|
2008-03-05 21:08:09 +08:00
|
|
|
|
2008-10-14 07:45:24 +08:00
|
|
|
static int p9_fd_write(struct p9_client *client, void *v, int len)
|
2008-02-07 09:25:03 +08:00
|
|
|
{
|
2008-10-14 07:45:24 +08:00
|
|
|
int ret;
|
|
|
|
mm_segment_t oldfs;
|
|
|
|
struct p9_trans_fd *ts = NULL;
|
2008-02-07 09:25:03 +08:00
|
|
|
|
2008-10-14 07:45:24 +08:00
|
|
|
if (client && client->status != Disconnected)
|
|
|
|
ts = client->trans;
|
2008-02-07 09:25:03 +08:00
|
|
|
|
2008-10-14 07:45:24 +08:00
|
|
|
if (!ts)
|
|
|
|
return -EREMOTEIO;
|
2008-02-07 09:25:03 +08:00
|
|
|
|
2008-10-14 07:45:24 +08:00
|
|
|
if (!(ts->wr->f_flags & O_NONBLOCK))
|
|
|
|
P9_DPRINTK(P9_DEBUG_ERROR, "blocking write ...\n");
|
2008-10-14 07:45:25 +08:00
|
|
|
|
2008-10-14 07:45:24 +08:00
|
|
|
oldfs = get_fs();
|
|
|
|
set_fs(get_ds());
|
|
|
|
/* The cast to a user pointer is valid due to the set_fs() */
|
|
|
|
ret = vfs_write(ts->wr, (void __user *)v, len, &ts->wr->f_pos);
|
|
|
|
set_fs(oldfs);
|
2008-10-14 07:45:25 +08:00
|
|
|
|
2008-10-14 07:45:24 +08:00
|
|
|
if (ret <= 0 && ret != -ERESTARTSYS && ret != -EAGAIN)
|
|
|
|
client->status = Disconnected;
|
|
|
|
return ret;
|
2008-02-07 09:25:03 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* p9_write_work - called when a transport can send some data
|
2008-03-05 21:08:09 +08:00
|
|
|
* @work: container for work to be done
|
|
|
|
*
|
2008-02-07 09:25:03 +08:00
|
|
|
*/
|
2008-03-05 21:08:09 +08:00
|
|
|
|
2008-02-07 09:25:03 +08:00
|
|
|
static void p9_write_work(struct work_struct *work)
|
|
|
|
{
|
|
|
|
int n, err;
|
|
|
|
struct p9_conn *m;
|
2008-10-14 07:45:22 +08:00
|
|
|
struct p9_req_t *req;
|
2008-02-07 09:25:03 +08:00
|
|
|
|
|
|
|
m = container_of(work, struct p9_conn, wq);
|
|
|
|
|
|
|
|
if (m->err < 0) {
|
|
|
|
clear_bit(Wworksched, &m->wsched);
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (!m->wsize) {
|
|
|
|
if (list_empty(&m->unsent_req_list)) {
|
|
|
|
clear_bit(Wworksched, &m->wsched);
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2008-10-14 07:45:22 +08:00
|
|
|
spin_lock(&m->client->lock);
|
|
|
|
req = list_entry(m->unsent_req_list.next, struct p9_req_t,
|
2008-02-07 09:25:03 +08:00
|
|
|
req_list);
|
2008-10-14 07:45:22 +08:00
|
|
|
req->status = REQ_STATUS_SENT;
|
2008-02-07 09:25:03 +08:00
|
|
|
list_move_tail(&req->req_list, &m->req_list);
|
|
|
|
|
2008-10-14 07:45:22 +08:00
|
|
|
m->wbuf = req->tc->sdata;
|
|
|
|
m->wsize = req->tc->size;
|
2008-02-07 09:25:03 +08:00
|
|
|
m->wpos = 0;
|
2008-10-14 07:45:22 +08:00
|
|
|
spin_unlock(&m->client->lock);
|
2008-02-07 09:25:03 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
P9_DPRINTK(P9_DEBUG_MUX, "mux %p pos %d size %d\n", m, m->wpos,
|
|
|
|
m->wsize);
|
|
|
|
clear_bit(Wpending, &m->wsched);
|
2008-10-14 07:45:25 +08:00
|
|
|
err = p9_fd_write(m->client, m->wbuf + m->wpos, m->wsize - m->wpos);
|
2008-02-07 09:25:03 +08:00
|
|
|
P9_DPRINTK(P9_DEBUG_MUX, "mux %p sent %d bytes\n", m, err);
|
|
|
|
if (err == -EAGAIN) {
|
|
|
|
clear_bit(Wworksched, &m->wsched);
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (err < 0)
|
|
|
|
goto error;
|
|
|
|
else if (err == 0) {
|
|
|
|
err = -EREMOTEIO;
|
|
|
|
goto error;
|
|
|
|
}
|
|
|
|
|
|
|
|
m->wpos += err;
|
|
|
|
if (m->wpos == m->wsize)
|
|
|
|
m->wpos = m->wsize = 0;
|
|
|
|
|
|
|
|
if (m->wsize == 0 && !list_empty(&m->unsent_req_list)) {
|
|
|
|
if (test_and_clear_bit(Wpending, &m->wsched))
|
|
|
|
n = POLLOUT;
|
|
|
|
else
|
2008-10-14 07:45:25 +08:00
|
|
|
n = p9_fd_poll(m->client, NULL);
|
2008-02-07 09:25:03 +08:00
|
|
|
|
|
|
|
if (n & POLLOUT) {
|
|
|
|
P9_DPRINTK(P9_DEBUG_MUX, "schedule write work %p\n", m);
|
|
|
|
queue_work(p9_mux_wq, &m->wq);
|
|
|
|
} else
|
|
|
|
clear_bit(Wworksched, &m->wsched);
|
|
|
|
} else
|
|
|
|
clear_bit(Wworksched, &m->wsched);
|
|
|
|
|
|
|
|
return;
|
|
|
|
|
|
|
|
error:
|
|
|
|
p9_conn_cancel(m, err);
|
|
|
|
clear_bit(Wworksched, &m->wsched);
|
|
|
|
}
|
|
|
|
|
2008-10-14 07:45:24 +08:00
|
|
|
static int p9_pollwake(wait_queue_t *wait, unsigned mode, int sync, void *key)
|
2008-02-07 09:25:03 +08:00
|
|
|
{
|
2008-10-14 07:45:24 +08:00
|
|
|
struct p9_poll_wait *pwait =
|
|
|
|
container_of(wait, struct p9_poll_wait, wait);
|
|
|
|
struct p9_conn *m = pwait->conn;
|
|
|
|
unsigned long flags;
|
|
|
|
DECLARE_WAITQUEUE(dummy_wait, p9_poll_task);
|
2008-02-07 09:25:03 +08:00
|
|
|
|
2008-10-14 07:45:24 +08:00
|
|
|
spin_lock_irqsave(&p9_poll_lock, flags);
|
|
|
|
if (list_empty(&m->poll_pending_link))
|
|
|
|
list_add_tail(&m->poll_pending_link, &p9_poll_pending_list);
|
|
|
|
spin_unlock_irqrestore(&p9_poll_lock, flags);
|
2008-02-07 09:25:03 +08:00
|
|
|
|
2008-10-14 07:45:24 +08:00
|
|
|
/* perform the default wake up operation */
|
|
|
|
return default_wake_function(&dummy_wait, mode, sync, key);
|
2008-02-07 09:25:03 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
2008-10-14 07:45:24 +08:00
|
|
|
* p9_pollwait - add poll task to the wait queue
|
|
|
|
* @filp: file pointer being polled
|
|
|
|
* @wait_address: wait_q to block on
|
|
|
|
* @p: poll state
|
2008-03-05 21:08:09 +08:00
|
|
|
*
|
2008-10-14 07:45:24 +08:00
|
|
|
* called by files poll operation to add v9fs-poll task to files wait queue
|
2008-02-07 09:25:03 +08:00
|
|
|
*/
|
2008-03-05 21:08:09 +08:00
|
|
|
|
2008-10-14 07:45:24 +08:00
|
|
|
static void
|
|
|
|
p9_pollwait(struct file *filp, wait_queue_head_t *wait_address, poll_table *p)
|
2008-02-07 09:25:03 +08:00
|
|
|
{
|
2008-10-14 07:45:24 +08:00
|
|
|
struct p9_conn *m = container_of(p, struct p9_conn, pt);
|
|
|
|
struct p9_poll_wait *pwait = NULL;
|
|
|
|
int i;
|
2008-02-07 09:25:03 +08:00
|
|
|
|
2008-10-14 07:45:24 +08:00
|
|
|
for (i = 0; i < ARRAY_SIZE(m->poll_wait); i++) {
|
|
|
|
if (m->poll_wait[i].wait_addr == NULL) {
|
|
|
|
pwait = &m->poll_wait[i];
|
|
|
|
break;
|
2008-02-07 09:25:03 +08:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2008-10-14 07:45:24 +08:00
|
|
|
if (!pwait) {
|
|
|
|
P9_DPRINTK(P9_DEBUG_ERROR, "not enough wait_address slots\n");
|
2008-02-07 09:25:03 +08:00
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2008-10-14 07:45:24 +08:00
|
|
|
if (!wait_address) {
|
|
|
|
P9_DPRINTK(P9_DEBUG_ERROR, "no wait_address\n");
|
|
|
|
pwait->wait_addr = ERR_PTR(-EIO);
|
|
|
|
return;
|
|
|
|
}
|
2008-02-07 09:25:03 +08:00
|
|
|
|
2008-10-14 07:45:24 +08:00
|
|
|
pwait->conn = m;
|
|
|
|
pwait->wait_addr = wait_address;
|
|
|
|
init_waitqueue_func_entry(&pwait->wait, p9_pollwake);
|
|
|
|
add_wait_queue(wait_address, &pwait->wait);
|
|
|
|
}
|
2008-02-07 09:25:03 +08:00
|
|
|
|
2008-10-14 07:45:24 +08:00
|
|
|
/**
|
|
|
|
* p9_conn_create - allocate and initialize the per-session mux data
|
|
|
|
* @client: client instance
|
|
|
|
*
|
|
|
|
* Note: Creates the polling task if this is the first session.
|
|
|
|
*/
|
2008-02-07 09:25:03 +08:00
|
|
|
|
2008-10-14 07:45:24 +08:00
|
|
|
static struct p9_conn *p9_conn_create(struct p9_client *client)
|
|
|
|
{
|
|
|
|
int i, n;
|
|
|
|
struct p9_conn *m;
|
2008-02-07 09:25:03 +08:00
|
|
|
|
2008-10-14 07:45:24 +08:00
|
|
|
P9_DPRINTK(P9_DEBUG_MUX, "client %p msize %d\n", client, client->msize);
|
|
|
|
m = kzalloc(sizeof(struct p9_conn), GFP_KERNEL);
|
|
|
|
if (!m)
|
|
|
|
return ERR_PTR(-ENOMEM);
|
2008-02-07 09:25:03 +08:00
|
|
|
|
2008-10-14 07:45:24 +08:00
|
|
|
INIT_LIST_HEAD(&m->mux_list);
|
|
|
|
m->client = client;
|
2008-02-07 09:25:03 +08:00
|
|
|
|
2008-10-14 07:45:24 +08:00
|
|
|
INIT_LIST_HEAD(&m->req_list);
|
|
|
|
INIT_LIST_HEAD(&m->unsent_req_list);
|
|
|
|
INIT_WORK(&m->rq, p9_read_work);
|
|
|
|
INIT_WORK(&m->wq, p9_write_work);
|
|
|
|
INIT_LIST_HEAD(&m->poll_pending_link);
|
|
|
|
init_poll_funcptr(&m->pt, p9_pollwait);
|
2008-02-07 09:25:03 +08:00
|
|
|
|
2008-10-14 07:45:24 +08:00
|
|
|
n = p9_fd_poll(client, &m->pt);
|
|
|
|
if (n & POLLIN) {
|
|
|
|
P9_DPRINTK(P9_DEBUG_MUX, "mux %p can read\n", m);
|
|
|
|
set_bit(Rpending, &m->wsched);
|
|
|
|
}
|
2008-02-07 09:25:03 +08:00
|
|
|
|
2008-10-14 07:45:24 +08:00
|
|
|
if (n & POLLOUT) {
|
|
|
|
P9_DPRINTK(P9_DEBUG_MUX, "mux %p can write\n", m);
|
|
|
|
set_bit(Wpending, &m->wsched);
|
|
|
|
}
|
|
|
|
|
|
|
|
for (i = 0; i < ARRAY_SIZE(m->poll_wait); i++) {
|
|
|
|
if (IS_ERR(m->poll_wait[i].wait_addr)) {
|
|
|
|
p9_mux_poll_stop(m);
|
|
|
|
kfree(m);
|
|
|
|
/* return the error code */
|
|
|
|
return (void *)m->poll_wait[i].wait_addr;
|
2008-02-07 09:25:03 +08:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2008-10-14 07:45:24 +08:00
|
|
|
return m;
|
|
|
|
}
|
2008-02-07 09:25:03 +08:00
|
|
|
|
2008-10-14 07:45:24 +08:00
|
|
|
/**
|
|
|
|
* p9_poll_mux - polls a mux and schedules read or write works if necessary
|
|
|
|
* @m: connection to poll
|
|
|
|
*
|
|
|
|
*/
|
|
|
|
|
|
|
|
static void p9_poll_mux(struct p9_conn *m)
|
|
|
|
{
|
|
|
|
int n;
|
|
|
|
|
|
|
|
if (m->err < 0)
|
|
|
|
return;
|
|
|
|
|
|
|
|
n = p9_fd_poll(m->client, NULL);
|
|
|
|
if (n < 0 || n & (POLLERR | POLLHUP | POLLNVAL)) {
|
|
|
|
P9_DPRINTK(P9_DEBUG_MUX, "error mux %p err %d\n", m, n);
|
|
|
|
if (n >= 0)
|
|
|
|
n = -ECONNRESET;
|
|
|
|
p9_conn_cancel(m, n);
|
|
|
|
}
|
|
|
|
|
|
|
|
if (n & POLLIN) {
|
|
|
|
set_bit(Rpending, &m->wsched);
|
|
|
|
P9_DPRINTK(P9_DEBUG_MUX, "mux %p can read\n", m);
|
|
|
|
if (!test_and_set_bit(Rworksched, &m->wsched)) {
|
2008-02-07 09:25:03 +08:00
|
|
|
P9_DPRINTK(P9_DEBUG_MUX, "schedule read work %p\n", m);
|
|
|
|
queue_work(p9_mux_wq, &m->rq);
|
2008-10-14 07:45:24 +08:00
|
|
|
}
|
|
|
|
}
|
2008-02-07 09:25:03 +08:00
|
|
|
|
2008-10-14 07:45:24 +08:00
|
|
|
if (n & POLLOUT) {
|
|
|
|
set_bit(Wpending, &m->wsched);
|
|
|
|
P9_DPRINTK(P9_DEBUG_MUX, "mux %p can write\n", m);
|
|
|
|
if ((m->wsize || !list_empty(&m->unsent_req_list))
|
|
|
|
&& !test_and_set_bit(Wworksched, &m->wsched)) {
|
|
|
|
P9_DPRINTK(P9_DEBUG_MUX, "schedule write work %p\n", m);
|
|
|
|
queue_work(p9_mux_wq, &m->wq);
|
|
|
|
}
|
|
|
|
}
|
2008-02-07 09:25:03 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
2008-10-14 07:45:21 +08:00
|
|
|
* p9_fd_request - send 9P request
|
2008-02-07 09:25:03 +08:00
|
|
|
* The function can sleep until the request is scheduled for sending.
|
|
|
|
* The function can be interrupted. Return from the function is not
|
2008-10-14 07:45:21 +08:00
|
|
|
* a guarantee that the request is sent successfully.
|
2008-02-07 09:25:03 +08:00
|
|
|
*
|
2008-10-14 07:45:21 +08:00
|
|
|
* @client: client instance
|
|
|
|
* @req: request to be sent
|
2008-03-05 21:08:09 +08:00
|
|
|
*
|
2008-02-07 09:25:03 +08:00
|
|
|
*/
|
2008-03-05 21:08:09 +08:00
|
|
|
|
2008-10-14 07:45:21 +08:00
|
|
|
static int p9_fd_request(struct p9_client *client, struct p9_req_t *req)
|
2008-02-07 09:25:03 +08:00
|
|
|
{
|
|
|
|
int n;
|
2008-10-14 07:45:21 +08:00
|
|
|
struct p9_trans_fd *ts = client->trans;
|
|
|
|
struct p9_conn *m = ts->conn;
|
2008-02-07 09:25:03 +08:00
|
|
|
|
|
|
|
P9_DPRINTK(P9_DEBUG_MUX, "mux %p task %p tcall %p id %d\n", m, current,
|
2008-10-14 07:45:21 +08:00
|
|
|
req->tc, req->tc->id);
|
2008-02-07 09:25:03 +08:00
|
|
|
if (m->err < 0)
|
2008-10-14 07:45:21 +08:00
|
|
|
return m->err;
|
2008-02-07 09:25:03 +08:00
|
|
|
|
|
|
|
#ifdef CONFIG_NET_9P_DEBUG
|
|
|
|
if ((p9_debug_level&P9_DEBUG_FCALL) == P9_DEBUG_FCALL) {
|
|
|
|
char buf[150];
|
|
|
|
|
2008-10-14 07:45:21 +08:00
|
|
|
p9_printfcall(buf, sizeof(buf), req->tc, client->dotu);
|
2008-02-07 09:25:03 +08:00
|
|
|
printk(KERN_NOTICE "<<< %p %s\n", m, buf);
|
|
|
|
}
|
|
|
|
#endif
|
|
|
|
|
2008-10-14 07:45:22 +08:00
|
|
|
req->status = REQ_STATUS_UNSENT;
|
2008-02-07 09:25:03 +08:00
|
|
|
|
2008-10-14 07:45:21 +08:00
|
|
|
spin_lock(&client->lock);
|
2008-02-07 09:25:03 +08:00
|
|
|
list_add_tail(&req->req_list, &m->unsent_req_list);
|
2008-10-14 07:45:21 +08:00
|
|
|
spin_unlock(&client->lock);
|
2008-02-07 09:25:03 +08:00
|
|
|
|
|
|
|
if (test_and_clear_bit(Wpending, &m->wsched))
|
|
|
|
n = POLLOUT;
|
|
|
|
else
|
2008-10-14 07:45:25 +08:00
|
|
|
n = p9_fd_poll(m->client, NULL);
|
2008-02-07 09:25:03 +08:00
|
|
|
|
|
|
|
if (n & POLLOUT && !test_and_set_bit(Wworksched, &m->wsched))
|
|
|
|
queue_work(p9_mux_wq, &m->wq);
|
|
|
|
|
2008-10-14 07:45:21 +08:00
|
|
|
return 0;
|
2008-02-07 09:25:03 +08:00
|
|
|
}
|
|
|
|
|
2008-10-14 07:45:21 +08:00
|
|
|
static int p9_fd_cancel(struct p9_client *client, struct p9_req_t *req)
|
2008-02-07 09:25:03 +08:00
|
|
|
{
|
2008-10-14 07:45:21 +08:00
|
|
|
struct p9_trans_fd *ts = client->trans;
|
|
|
|
struct p9_conn *m = ts->conn;
|
2008-02-07 09:25:03 +08:00
|
|
|
|
|
|
|
P9_DPRINTK(P9_DEBUG_MUX, "mux %p req %p tag %d\n", m, req, req->tag);
|
|
|
|
|
2008-10-14 07:45:21 +08:00
|
|
|
spin_lock(&client->lock);
|
|
|
|
list_del(&req->req_list);
|
|
|
|
spin_unlock(&client->lock);
|
|
|
|
|
2008-02-07 09:25:03 +08:00
|
|
|
/* if a response was received for a request, do nothing */
|
2008-10-14 07:45:22 +08:00
|
|
|
if (req->rc || req->t_err) {
|
2008-02-07 09:25:03 +08:00
|
|
|
P9_DPRINTK(P9_DEBUG_MUX,
|
|
|
|
"mux %p req %p response already received\n", m, req);
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
2008-10-14 07:45:21 +08:00
|
|
|
if (req->status == REQ_STATUS_UNSENT) {
|
|
|
|
req->status = REQ_STATUS_FLSHD;
|
|
|
|
return 0;
|
2008-02-07 09:25:03 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
return 1;
|
|
|
|
}
|
|
|
|
|
2007-10-18 03:31:07 +08:00
|
|
|
/**
|
2008-03-08 00:53:53 +08:00
|
|
|
* parse_options - parse mount options into session structure
|
2007-10-18 03:31:07 +08:00
|
|
|
* @options: options string passed from mount
|
2008-03-05 21:08:09 +08:00
|
|
|
* @opts: transport-specific structure to parse options into
|
2007-10-18 03:31:07 +08:00
|
|
|
*
|
2008-03-08 00:53:53 +08:00
|
|
|
* Returns 0 upon success, -ERRNO upon failure
|
2007-10-18 03:31:07 +08:00
|
|
|
*/
|
2007-07-11 06:57:28 +08:00
|
|
|
|
2008-03-08 00:53:53 +08:00
|
|
|
static int parse_opts(char *params, struct p9_fd_opts *opts)
|
2007-07-11 06:57:28 +08:00
|
|
|
{
|
2007-10-18 03:31:07 +08:00
|
|
|
char *p;
|
|
|
|
substring_t args[MAX_OPT_ARGS];
|
|
|
|
int option;
|
2008-03-08 00:53:53 +08:00
|
|
|
char *options;
|
2007-10-18 03:31:07 +08:00
|
|
|
int ret;
|
2007-07-11 06:57:28 +08:00
|
|
|
|
2007-10-18 03:31:07 +08:00
|
|
|
opts->port = P9_PORT;
|
|
|
|
opts->rfd = ~0;
|
|
|
|
opts->wfd = ~0;
|
2007-07-11 06:57:28 +08:00
|
|
|
|
2008-03-08 00:53:53 +08:00
|
|
|
if (!params)
|
|
|
|
return 0;
|
|
|
|
|
|
|
|
options = kstrdup(params, GFP_KERNEL);
|
|
|
|
if (!options) {
|
|
|
|
P9_DPRINTK(P9_DEBUG_ERROR,
|
|
|
|
"failed to allocate copy of option string\n");
|
|
|
|
return -ENOMEM;
|
|
|
|
}
|
2007-07-11 06:57:28 +08:00
|
|
|
|
2007-10-18 03:31:07 +08:00
|
|
|
while ((p = strsep(&options, ",")) != NULL) {
|
|
|
|
int token;
|
2008-03-08 00:53:53 +08:00
|
|
|
int r;
|
2007-10-18 03:31:07 +08:00
|
|
|
if (!*p)
|
|
|
|
continue;
|
|
|
|
token = match_token(p, tokens, args);
|
2008-03-08 00:53:53 +08:00
|
|
|
r = match_int(&args[0], &option);
|
|
|
|
if (r < 0) {
|
2007-10-18 03:31:07 +08:00
|
|
|
P9_DPRINTK(P9_DEBUG_ERROR,
|
|
|
|
"integer field, but no integer?\n");
|
2008-03-08 00:53:53 +08:00
|
|
|
ret = r;
|
2007-10-18 03:31:07 +08:00
|
|
|
continue;
|
|
|
|
}
|
|
|
|
switch (token) {
|
|
|
|
case Opt_port:
|
|
|
|
opts->port = option;
|
|
|
|
break;
|
|
|
|
case Opt_rfdno:
|
|
|
|
opts->rfd = option;
|
|
|
|
break;
|
|
|
|
case Opt_wfdno:
|
|
|
|
opts->wfd = option;
|
|
|
|
break;
|
|
|
|
default:
|
|
|
|
continue;
|
|
|
|
}
|
2007-07-11 06:57:28 +08:00
|
|
|
}
|
2008-03-08 00:53:53 +08:00
|
|
|
kfree(options);
|
|
|
|
return 0;
|
2007-07-11 06:57:28 +08:00
|
|
|
}
|
|
|
|
|
2008-10-14 07:45:25 +08:00
|
|
|
static int p9_fd_open(struct p9_client *client, int rfd, int wfd)
|
2007-07-11 06:57:28 +08:00
|
|
|
{
|
2007-10-18 03:31:07 +08:00
|
|
|
struct p9_trans_fd *ts = kmalloc(sizeof(struct p9_trans_fd),
|
|
|
|
GFP_KERNEL);
|
|
|
|
if (!ts)
|
|
|
|
return -ENOMEM;
|
2007-07-11 06:57:28 +08:00
|
|
|
|
2007-10-18 03:31:07 +08:00
|
|
|
ts->rd = fget(rfd);
|
|
|
|
ts->wr = fget(wfd);
|
|
|
|
if (!ts->rd || !ts->wr) {
|
|
|
|
if (ts->rd)
|
|
|
|
fput(ts->rd);
|
|
|
|
if (ts->wr)
|
|
|
|
fput(ts->wr);
|
|
|
|
kfree(ts);
|
|
|
|
return -EIO;
|
2007-07-11 06:57:28 +08:00
|
|
|
}
|
|
|
|
|
2008-10-14 07:45:25 +08:00
|
|
|
client->trans = ts;
|
|
|
|
client->status = Connected;
|
2007-07-11 06:57:28 +08:00
|
|
|
|
2007-10-18 03:31:07 +08:00
|
|
|
return 0;
|
2007-07-11 06:57:28 +08:00
|
|
|
}
|
|
|
|
|
2008-10-14 07:45:25 +08:00
|
|
|
static int p9_socket_open(struct p9_client *client, struct socket *csocket)
|
2007-07-11 06:57:28 +08:00
|
|
|
{
|
|
|
|
int fd, ret;
|
|
|
|
|
|
|
|
csocket->sk->sk_allocation = GFP_NOIO;
|
flag parameters: socket and socketpair
This patch adds support for flag values which are ORed to the type passwd
to socket and socketpair. The additional code is minimal. The flag
values in this implementation can and must match the O_* flags. This
avoids overhead in the conversion.
The internal functions sock_alloc_fd and sock_map_fd get a new parameters
and all callers are changed.
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
#include <fcntl.h>
#include <stdio.h>
#include <unistd.h>
#include <netinet/in.h>
#include <sys/socket.h>
#define PORT 57392
/* For Linux these must be the same. */
#define SOCK_CLOEXEC O_CLOEXEC
int
main (void)
{
int fd;
fd = socket (PF_INET, SOCK_STREAM, 0);
if (fd == -1)
{
puts ("socket(0) failed");
return 1;
}
int coe = fcntl (fd, F_GETFD);
if (coe == -1)
{
puts ("fcntl failed");
return 1;
}
if (coe & FD_CLOEXEC)
{
puts ("socket(0) set close-on-exec flag");
return 1;
}
close (fd);
fd = socket (PF_INET, SOCK_STREAM|SOCK_CLOEXEC, 0);
if (fd == -1)
{
puts ("socket(SOCK_CLOEXEC) failed");
return 1;
}
coe = fcntl (fd, F_GETFD);
if (coe == -1)
{
puts ("fcntl failed");
return 1;
}
if ((coe & FD_CLOEXEC) == 0)
{
puts ("socket(SOCK_CLOEXEC) does not set close-on-exec flag");
return 1;
}
close (fd);
int fds[2];
if (socketpair (PF_UNIX, SOCK_STREAM, 0, fds) == -1)
{
puts ("socketpair(0) failed");
return 1;
}
for (int i = 0; i < 2; ++i)
{
coe = fcntl (fds[i], F_GETFD);
if (coe == -1)
{
puts ("fcntl failed");
return 1;
}
if (coe & FD_CLOEXEC)
{
printf ("socketpair(0) set close-on-exec flag for fds[%d]\n", i);
return 1;
}
close (fds[i]);
}
if (socketpair (PF_UNIX, SOCK_STREAM|SOCK_CLOEXEC, 0, fds) == -1)
{
puts ("socketpair(SOCK_CLOEXEC) failed");
return 1;
}
for (int i = 0; i < 2; ++i)
{
coe = fcntl (fds[i], F_GETFD);
if (coe == -1)
{
puts ("fcntl failed");
return 1;
}
if ((coe & FD_CLOEXEC) == 0)
{
printf ("socketpair(SOCK_CLOEXEC) does not set close-on-exec flag for fds[%d]\n", i);
return 1;
}
close (fds[i]);
}
puts ("OK");
return 0;
}
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Signed-off-by: Ulrich Drepper <drepper@redhat.com>
Acked-by: Davide Libenzi <davidel@xmailserver.org>
Cc: Michael Kerrisk <mtk.manpages@googlemail.com>
Cc: "David S. Miller" <davem@davemloft.net>
Cc: Ralf Baechle <ralf@linux-mips.org>
Signed-off-by: Andrew Morton <akpm@linux-foundation.org>
Signed-off-by: Linus Torvalds <torvalds@linux-foundation.org>
2008-07-24 12:29:17 +08:00
|
|
|
fd = sock_map_fd(csocket, 0);
|
2007-07-11 06:57:28 +08:00
|
|
|
if (fd < 0) {
|
|
|
|
P9_EPRINTK(KERN_ERR, "p9_socket_open: failed to map fd\n");
|
|
|
|
return fd;
|
|
|
|
}
|
|
|
|
|
2008-10-14 07:45:25 +08:00
|
|
|
ret = p9_fd_open(client, fd, fd);
|
2007-07-11 06:57:28 +08:00
|
|
|
if (ret < 0) {
|
|
|
|
P9_EPRINTK(KERN_ERR, "p9_socket_open: failed to open fd\n");
|
|
|
|
sockfd_put(csocket);
|
|
|
|
return ret;
|
|
|
|
}
|
|
|
|
|
2008-10-14 07:45:25 +08:00
|
|
|
((struct p9_trans_fd *)client->trans)->rd->f_flags |= O_NONBLOCK;
|
2007-07-11 06:57:28 +08:00
|
|
|
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
2008-10-14 07:45:24 +08:00
|
|
|
* p9_mux_destroy - cancels all pending requests and frees mux resources
|
|
|
|
* @m: mux to destroy
|
2007-07-11 06:57:28 +08:00
|
|
|
*
|
|
|
|
*/
|
2008-03-05 21:08:09 +08:00
|
|
|
|
2008-10-14 07:45:24 +08:00
|
|
|
static void p9_conn_destroy(struct p9_conn *m)
|
2007-07-11 06:57:28 +08:00
|
|
|
{
|
2008-10-14 07:45:24 +08:00
|
|
|
P9_DPRINTK(P9_DEBUG_MUX, "mux %p prev %p next %p\n", m,
|
|
|
|
m->mux_list.prev, m->mux_list.next);
|
2007-07-11 06:57:28 +08:00
|
|
|
|
2008-10-14 07:45:24 +08:00
|
|
|
p9_mux_poll_stop(m);
|
|
|
|
cancel_work_sync(&m->rq);
|
|
|
|
cancel_work_sync(&m->wq);
|
2007-07-11 06:57:28 +08:00
|
|
|
|
2008-10-14 07:45:24 +08:00
|
|
|
p9_conn_cancel(m, -ECONNRESET);
|
2007-07-11 06:57:28 +08:00
|
|
|
|
2008-10-14 07:45:24 +08:00
|
|
|
m->client = NULL;
|
|
|
|
kfree(m);
|
2007-07-11 06:57:28 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
2008-10-14 07:45:25 +08:00
|
|
|
* p9_fd_close - shutdown file descriptor transport
|
|
|
|
* @client: client instance
|
2007-07-11 06:57:28 +08:00
|
|
|
*
|
|
|
|
*/
|
2008-03-05 21:08:09 +08:00
|
|
|
|
2008-10-14 07:45:25 +08:00
|
|
|
static void p9_fd_close(struct p9_client *client)
|
2007-07-11 06:57:28 +08:00
|
|
|
{
|
|
|
|
struct p9_trans_fd *ts;
|
|
|
|
|
2008-10-14 07:45:25 +08:00
|
|
|
if (!client)
|
2007-07-11 06:57:28 +08:00
|
|
|
return;
|
|
|
|
|
2008-10-14 07:45:25 +08:00
|
|
|
ts = client->trans;
|
2007-07-11 06:57:28 +08:00
|
|
|
if (!ts)
|
|
|
|
return;
|
|
|
|
|
2008-10-14 07:45:25 +08:00
|
|
|
client->status = Disconnected;
|
|
|
|
|
2008-02-07 09:25:03 +08:00
|
|
|
p9_conn_destroy(ts->conn);
|
|
|
|
|
2007-07-11 06:57:28 +08:00
|
|
|
if (ts->rd)
|
|
|
|
fput(ts->rd);
|
|
|
|
if (ts->wr)
|
|
|
|
fput(ts->wr);
|
2008-10-14 07:45:25 +08:00
|
|
|
|
2007-07-11 06:57:28 +08:00
|
|
|
kfree(ts);
|
|
|
|
}
|
|
|
|
|
2008-05-09 09:26:37 +08:00
|
|
|
/*
|
|
|
|
* stolen from NFS - maybe should be made a generic function?
|
|
|
|
*/
|
|
|
|
static inline int valid_ipaddr4(const char *buf)
|
|
|
|
{
|
|
|
|
int rc, count, in[4];
|
|
|
|
|
|
|
|
rc = sscanf(buf, "%d.%d.%d.%d", &in[0], &in[1], &in[2], &in[3]);
|
|
|
|
if (rc != 4)
|
|
|
|
return -EINVAL;
|
|
|
|
for (count = 0; count < 4; count++) {
|
|
|
|
if (in[count] > 255)
|
|
|
|
return -EINVAL;
|
|
|
|
}
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
2008-10-14 07:45:25 +08:00
|
|
|
static int
|
|
|
|
p9_fd_create_tcp(struct p9_client *client, const char *addr, char *args)
|
2007-10-18 03:31:07 +08:00
|
|
|
{
|
|
|
|
int err;
|
|
|
|
struct socket *csocket;
|
|
|
|
struct sockaddr_in sin_server;
|
|
|
|
struct p9_fd_opts opts;
|
2008-10-14 07:45:25 +08:00
|
|
|
struct p9_trans_fd *p = NULL; /* this gets allocated in p9_fd_open */
|
2007-10-18 03:31:07 +08:00
|
|
|
|
2008-03-08 00:53:53 +08:00
|
|
|
err = parse_opts(args, &opts);
|
|
|
|
if (err < 0)
|
2008-10-14 07:45:25 +08:00
|
|
|
return err;
|
2007-10-18 03:31:07 +08:00
|
|
|
|
2008-05-09 09:26:37 +08:00
|
|
|
if (valid_ipaddr4(addr) < 0)
|
2008-10-14 07:45:25 +08:00
|
|
|
return -EINVAL;
|
2008-05-09 09:26:37 +08:00
|
|
|
|
2007-10-18 03:31:07 +08:00
|
|
|
csocket = NULL;
|
|
|
|
|
|
|
|
sin_server.sin_family = AF_INET;
|
|
|
|
sin_server.sin_addr.s_addr = in_aton(addr);
|
|
|
|
sin_server.sin_port = htons(opts.port);
|
|
|
|
sock_create_kern(PF_INET, SOCK_STREAM, IPPROTO_TCP, &csocket);
|
|
|
|
|
|
|
|
if (!csocket) {
|
|
|
|
P9_EPRINTK(KERN_ERR, "p9_trans_tcp: problem creating socket\n");
|
|
|
|
err = -EIO;
|
|
|
|
goto error;
|
|
|
|
}
|
|
|
|
|
|
|
|
err = csocket->ops->connect(csocket,
|
|
|
|
(struct sockaddr *)&sin_server,
|
|
|
|
sizeof(struct sockaddr_in), 0);
|
|
|
|
if (err < 0) {
|
|
|
|
P9_EPRINTK(KERN_ERR,
|
|
|
|
"p9_trans_tcp: problem connecting socket to %s\n",
|
|
|
|
addr);
|
|
|
|
goto error;
|
|
|
|
}
|
|
|
|
|
2008-10-14 07:45:25 +08:00
|
|
|
err = p9_socket_open(client, csocket);
|
2007-10-18 03:31:07 +08:00
|
|
|
if (err < 0)
|
|
|
|
goto error;
|
|
|
|
|
2008-10-14 07:45:25 +08:00
|
|
|
p = (struct p9_trans_fd *) client->trans;
|
|
|
|
p->conn = p9_conn_create(client);
|
2008-02-07 09:25:03 +08:00
|
|
|
if (IS_ERR(p->conn)) {
|
|
|
|
err = PTR_ERR(p->conn);
|
|
|
|
p->conn = NULL;
|
|
|
|
goto error;
|
|
|
|
}
|
|
|
|
|
2008-10-14 07:45:25 +08:00
|
|
|
return 0;
|
2007-10-18 03:31:07 +08:00
|
|
|
|
|
|
|
error:
|
|
|
|
if (csocket)
|
|
|
|
sock_release(csocket);
|
|
|
|
|
2008-10-14 07:45:25 +08:00
|
|
|
kfree(p);
|
|
|
|
|
|
|
|
return err;
|
2007-10-18 03:31:07 +08:00
|
|
|
}
|
|
|
|
|
2008-10-14 07:45:25 +08:00
|
|
|
static int
|
|
|
|
p9_fd_create_unix(struct p9_client *client, const char *addr, char *args)
|
2007-10-18 03:31:07 +08:00
|
|
|
{
|
|
|
|
int err;
|
|
|
|
struct socket *csocket;
|
|
|
|
struct sockaddr_un sun_server;
|
2008-10-14 07:45:25 +08:00
|
|
|
struct p9_trans_fd *p = NULL; /* this gets allocated in p9_fd_open */
|
2007-10-18 03:31:07 +08:00
|
|
|
|
|
|
|
csocket = NULL;
|
|
|
|
|
|
|
|
if (strlen(addr) > UNIX_PATH_MAX) {
|
|
|
|
P9_EPRINTK(KERN_ERR, "p9_trans_unix: address too long: %s\n",
|
|
|
|
addr);
|
|
|
|
err = -ENAMETOOLONG;
|
|
|
|
goto error;
|
|
|
|
}
|
|
|
|
|
|
|
|
sun_server.sun_family = PF_UNIX;
|
|
|
|
strcpy(sun_server.sun_path, addr);
|
|
|
|
sock_create_kern(PF_UNIX, SOCK_STREAM, 0, &csocket);
|
|
|
|
err = csocket->ops->connect(csocket, (struct sockaddr *)&sun_server,
|
|
|
|
sizeof(struct sockaddr_un) - 1, 0);
|
|
|
|
if (err < 0) {
|
|
|
|
P9_EPRINTK(KERN_ERR,
|
|
|
|
"p9_trans_unix: problem connecting socket: %s: %d\n",
|
|
|
|
addr, err);
|
|
|
|
goto error;
|
|
|
|
}
|
|
|
|
|
2008-10-14 07:45:25 +08:00
|
|
|
err = p9_socket_open(client, csocket);
|
2007-10-18 03:31:07 +08:00
|
|
|
if (err < 0)
|
|
|
|
goto error;
|
|
|
|
|
2008-10-14 07:45:25 +08:00
|
|
|
p = (struct p9_trans_fd *) client->trans;
|
|
|
|
p->conn = p9_conn_create(client);
|
2008-02-07 09:25:03 +08:00
|
|
|
if (IS_ERR(p->conn)) {
|
|
|
|
err = PTR_ERR(p->conn);
|
|
|
|
p->conn = NULL;
|
|
|
|
goto error;
|
|
|
|
}
|
|
|
|
|
2008-10-14 07:45:25 +08:00
|
|
|
return 0;
|
2007-10-18 03:31:07 +08:00
|
|
|
|
|
|
|
error:
|
|
|
|
if (csocket)
|
|
|
|
sock_release(csocket);
|
|
|
|
|
2008-10-14 07:45:25 +08:00
|
|
|
kfree(p);
|
|
|
|
return err;
|
2007-10-18 03:31:07 +08:00
|
|
|
}
|
|
|
|
|
2008-10-14 07:45:25 +08:00
|
|
|
static int
|
|
|
|
p9_fd_create(struct p9_client *client, const char *addr, char *args)
|
2007-10-18 03:31:07 +08:00
|
|
|
{
|
|
|
|
int err;
|
|
|
|
struct p9_fd_opts opts;
|
2008-10-14 07:45:25 +08:00
|
|
|
struct p9_trans_fd *p = NULL; /* this get allocated in p9_fd_open */
|
2007-10-18 03:31:07 +08:00
|
|
|
|
|
|
|
parse_opts(args, &opts);
|
|
|
|
|
|
|
|
if (opts.rfd == ~0 || opts.wfd == ~0) {
|
|
|
|
printk(KERN_ERR "v9fs: Insufficient options for proto=fd\n");
|
2008-10-14 07:45:25 +08:00
|
|
|
return -ENOPROTOOPT;
|
2007-10-18 03:31:07 +08:00
|
|
|
}
|
|
|
|
|
2008-10-14 07:45:25 +08:00
|
|
|
err = p9_fd_open(client, opts.rfd, opts.wfd);
|
2007-10-18 03:31:07 +08:00
|
|
|
if (err < 0)
|
|
|
|
goto error;
|
|
|
|
|
2008-10-14 07:45:25 +08:00
|
|
|
p = (struct p9_trans_fd *) client->trans;
|
|
|
|
p->conn = p9_conn_create(client);
|
2008-02-07 09:25:03 +08:00
|
|
|
if (IS_ERR(p->conn)) {
|
|
|
|
err = PTR_ERR(p->conn);
|
|
|
|
p->conn = NULL;
|
|
|
|
goto error;
|
|
|
|
}
|
|
|
|
|
2008-10-14 07:45:25 +08:00
|
|
|
return 0;
|
2007-10-18 03:31:07 +08:00
|
|
|
|
|
|
|
error:
|
2008-10-14 07:45:25 +08:00
|
|
|
kfree(p);
|
|
|
|
return err;
|
2007-10-18 03:31:07 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
static struct p9_trans_module p9_tcp_trans = {
|
|
|
|
.name = "tcp",
|
|
|
|
.maxsize = MAX_SOCK_BUF,
|
|
|
|
.def = 1,
|
2008-10-14 07:45:25 +08:00
|
|
|
.create = p9_fd_create_tcp,
|
|
|
|
.close = p9_fd_close,
|
2008-10-14 07:45:21 +08:00
|
|
|
.request = p9_fd_request,
|
|
|
|
.cancel = p9_fd_cancel,
|
2008-09-25 05:22:23 +08:00
|
|
|
.owner = THIS_MODULE,
|
2007-10-18 03:31:07 +08:00
|
|
|
};
|
|
|
|
|
|
|
|
static struct p9_trans_module p9_unix_trans = {
|
|
|
|
.name = "unix",
|
|
|
|
.maxsize = MAX_SOCK_BUF,
|
|
|
|
.def = 0,
|
2008-10-14 07:45:25 +08:00
|
|
|
.create = p9_fd_create_unix,
|
|
|
|
.close = p9_fd_close,
|
2008-10-14 07:45:21 +08:00
|
|
|
.request = p9_fd_request,
|
|
|
|
.cancel = p9_fd_cancel,
|
2008-09-25 05:22:23 +08:00
|
|
|
.owner = THIS_MODULE,
|
2007-10-18 03:31:07 +08:00
|
|
|
};
|
|
|
|
|
|
|
|
static struct p9_trans_module p9_fd_trans = {
|
|
|
|
.name = "fd",
|
|
|
|
.maxsize = MAX_SOCK_BUF,
|
|
|
|
.def = 0,
|
2008-10-14 07:45:25 +08:00
|
|
|
.create = p9_fd_create,
|
|
|
|
.close = p9_fd_close,
|
2008-10-14 07:45:21 +08:00
|
|
|
.request = p9_fd_request,
|
|
|
|
.cancel = p9_fd_cancel,
|
2008-09-25 05:22:23 +08:00
|
|
|
.owner = THIS_MODULE,
|
2007-10-18 03:31:07 +08:00
|
|
|
};
|
|
|
|
|
2008-10-14 07:45:24 +08:00
|
|
|
/**
|
|
|
|
* p9_poll_proc - poll worker thread
|
|
|
|
* @a: thread state and arguments
|
|
|
|
*
|
|
|
|
* polls all v9fs transports for new events and queues the appropriate
|
|
|
|
* work to the work queue
|
|
|
|
*
|
|
|
|
*/
|
|
|
|
|
|
|
|
static int p9_poll_proc(void *a)
|
|
|
|
{
|
|
|
|
unsigned long flags;
|
|
|
|
|
|
|
|
P9_DPRINTK(P9_DEBUG_MUX, "start %p\n", current);
|
|
|
|
repeat:
|
|
|
|
spin_lock_irqsave(&p9_poll_lock, flags);
|
|
|
|
while (!list_empty(&p9_poll_pending_list)) {
|
|
|
|
struct p9_conn *conn = list_first_entry(&p9_poll_pending_list,
|
|
|
|
struct p9_conn,
|
|
|
|
poll_pending_link);
|
|
|
|
list_del_init(&conn->poll_pending_link);
|
|
|
|
spin_unlock_irqrestore(&p9_poll_lock, flags);
|
|
|
|
|
|
|
|
p9_poll_mux(conn);
|
|
|
|
|
|
|
|
spin_lock_irqsave(&p9_poll_lock, flags);
|
|
|
|
}
|
|
|
|
spin_unlock_irqrestore(&p9_poll_lock, flags);
|
|
|
|
|
|
|
|
set_current_state(TASK_INTERRUPTIBLE);
|
|
|
|
if (list_empty(&p9_poll_pending_list)) {
|
|
|
|
P9_DPRINTK(P9_DEBUG_MUX, "sleeping...\n");
|
|
|
|
schedule();
|
|
|
|
}
|
|
|
|
__set_current_state(TASK_RUNNING);
|
|
|
|
|
|
|
|
if (!kthread_should_stop())
|
|
|
|
goto repeat;
|
|
|
|
|
|
|
|
P9_DPRINTK(P9_DEBUG_MUX, "finish\n");
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
2008-05-09 09:26:37 +08:00
|
|
|
int p9_trans_fd_init(void)
|
2007-10-18 03:31:07 +08:00
|
|
|
{
|
2008-09-25 05:22:23 +08:00
|
|
|
p9_mux_wq = create_workqueue("v9fs");
|
|
|
|
if (!p9_mux_wq) {
|
|
|
|
printk(KERN_WARNING "v9fs: mux: creating workqueue failed\n");
|
|
|
|
return -ENOMEM;
|
2008-02-07 09:25:03 +08:00
|
|
|
}
|
|
|
|
|
2008-10-14 07:45:25 +08:00
|
|
|
p9_poll_task = kthread_run(p9_poll_proc, NULL, "v9fs-poll");
|
|
|
|
if (IS_ERR(p9_poll_task)) {
|
|
|
|
destroy_workqueue(p9_mux_wq);
|
|
|
|
printk(KERN_WARNING "v9fs: mux: creating poll task failed\n");
|
|
|
|
return PTR_ERR(p9_poll_task);
|
|
|
|
}
|
|
|
|
|
2007-10-18 03:31:07 +08:00
|
|
|
v9fs_register_trans(&p9_tcp_trans);
|
|
|
|
v9fs_register_trans(&p9_unix_trans);
|
|
|
|
v9fs_register_trans(&p9_fd_trans);
|
|
|
|
|
2008-03-29 05:15:57 +08:00
|
|
|
return 0;
|
2007-10-18 03:31:07 +08:00
|
|
|
}
|
2008-09-25 05:22:23 +08:00
|
|
|
|
|
|
|
void p9_trans_fd_exit(void)
|
|
|
|
{
|
2008-10-14 07:45:25 +08:00
|
|
|
kthread_stop(p9_poll_task);
|
2008-09-25 05:22:23 +08:00
|
|
|
v9fs_unregister_trans(&p9_tcp_trans);
|
|
|
|
v9fs_unregister_trans(&p9_unix_trans);
|
|
|
|
v9fs_unregister_trans(&p9_fd_trans);
|
2008-09-25 05:22:23 +08:00
|
|
|
|
|
|
|
destroy_workqueue(p9_mux_wq);
|
2008-09-25 05:22:23 +08:00
|
|
|
}
|