1/*
2 * linux/fs/9p/vfs_dir.c
3 *
4 * This file contains vfs directory ops for the 9P2000 protocol.
5 *
6 *  Copyright (C) 2004 by Eric Van Hensbergen <ericvh@gmail.com>
7 *  Copyright (C) 2002 by Ron Minnich <rminnich@lanl.gov>
8 *
9 *  This program is free software; you can redistribute it and/or modify
10 *  it under the terms of the GNU General Public License version 2
11 *  as published by the Free Software Foundation.
12 *
13 *  This program is distributed in the hope that it will be useful,
14 *  but WITHOUT ANY WARRANTY; without even the implied warranty of
15 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
16 *  GNU General Public License for more details.
17 *
18 *  You should have received a copy of the GNU General Public License
19 *  along with this program; if not, write to:
20 *  Free Software Foundation
21 *  51 Franklin Street, Fifth Floor
22 *  Boston, MA  02111-1301  USA
23 *
24 */
25
26#include <linux/module.h>
27#include <linux/errno.h>
28#include <linux/fs.h>
29#include <linux/file.h>
30#include <linux/stat.h>
31#include <linux/string.h>
32#include <linux/sched.h>
33#include <linux/inet.h>
34#include <linux/idr.h>
35#include <linux/slab.h>
36#include <net/9p/9p.h>
37#include <net/9p/client.h>
38
39#include "v9fs.h"
40#include "v9fs_vfs.h"
41#include "fid.h"
42
43/**
44 * struct p9_rdir - readdir accounting
45 * @mutex: mutex protecting readdir
46 * @head: start offset of current dirread buffer
47 * @tail: end offset of current dirread buffer
48 * @buf: dirread buffer
49 *
50 * private structure for keeping track of readdir
51 * allocated on demand
52 */
53
54struct p9_rdir {
55	struct mutex mutex;
56	int head;
57	int tail;
58	uint8_t *buf;
59};
60
61/**
62 * dt_type - return file type
63 * @mistat: mistat structure
64 *
65 */
66
67static inline int dt_type(struct p9_wstat *mistat)
68{
69	unsigned long perm = mistat->mode;
70	int rettype = DT_REG;
71
72	if (perm & P9_DMDIR)
73		rettype = DT_DIR;
74	if (perm & P9_DMSYMLINK)
75		rettype = DT_LNK;
76
77	return rettype;
78}
79
80static void p9stat_init(struct p9_wstat *stbuf)
81{
82	stbuf->name  = NULL;
83	stbuf->uid   = NULL;
84	stbuf->gid   = NULL;
85	stbuf->muid  = NULL;
86	stbuf->extension = NULL;
87}
88
89/**
90 * v9fs_alloc_rdir_buf - Allocate buffer used for read and readdir
91 * @filp: opened file structure
92 * @buflen: Length in bytes of buffer to allocate
93 *
94 */
95
96static int v9fs_alloc_rdir_buf(struct file *filp, int buflen)
97{
98	struct p9_rdir *rdir;
99	struct p9_fid *fid;
100	int err = 0;
101
102	fid = filp->private_data;
103	if (!fid->rdir) {
104		rdir = kmalloc(sizeof(struct p9_rdir) + buflen, GFP_KERNEL);
105
106		if (rdir == NULL) {
107			err = -ENOMEM;
108			goto exit;
109		}
110		spin_lock(&filp->f_dentry->d_lock);
111		if (!fid->rdir) {
112			rdir->buf = (uint8_t *)rdir + sizeof(struct p9_rdir);
113			mutex_init(&rdir->mutex);
114			rdir->head = rdir->tail = 0;
115			fid->rdir = (void *) rdir;
116			rdir = NULL;
117		}
118		spin_unlock(&filp->f_dentry->d_lock);
119		kfree(rdir);
120	}
121exit:
122	return err;
123}
124
125/**
126 * v9fs_dir_readdir - read a directory
127 * @filp: opened file structure
128 * @dirent: directory structure ???
129 * @filldir: function to populate directory structure ???
130 *
131 */
132
133static int v9fs_dir_readdir(struct file *filp, void *dirent, filldir_t filldir)
134{
135	int over;
136	struct p9_wstat st;
137	int err = 0;
138	struct p9_fid *fid;
139	int buflen;
140	int reclen = 0;
141	struct p9_rdir *rdir;
142
143	P9_DPRINTK(P9_DEBUG_VFS, "name %s\n", filp->f_path.dentry->d_name.name);
144	fid = filp->private_data;
145
146	buflen = fid->clnt->msize - P9_IOHDRSZ;
147
148	err = v9fs_alloc_rdir_buf(filp, buflen);
149	if (err)
150		goto exit;
151	rdir = (struct p9_rdir *) fid->rdir;
152
153	err = mutex_lock_interruptible(&rdir->mutex);
154	if (err)
155		return err;
156	while (err == 0) {
157		if (rdir->tail == rdir->head) {
158			err = v9fs_file_readn(filp, rdir->buf, NULL,
159							buflen, filp->f_pos);
160			if (err <= 0)
161				goto unlock_and_exit;
162
163			rdir->head = 0;
164			rdir->tail = err;
165		}
166		while (rdir->head < rdir->tail) {
167			p9stat_init(&st);
168			err = p9stat_read(rdir->buf + rdir->head,
169						rdir->tail - rdir->head, &st,
170						fid->clnt->proto_version);
171			if (err) {
172				P9_DPRINTK(P9_DEBUG_VFS, "returned %d\n", err);
173				err = -EIO;
174				p9stat_free(&st);
175				goto unlock_and_exit;
176			}
177			reclen = st.size+2;
178
179			over = filldir(dirent, st.name, strlen(st.name),
180			    filp->f_pos, v9fs_qid2ino(&st.qid), dt_type(&st));
181
182			p9stat_free(&st);
183
184			if (over) {
185				err = 0;
186				goto unlock_and_exit;
187			}
188			rdir->head += reclen;
189			filp->f_pos += reclen;
190		}
191	}
192
193unlock_and_exit:
194	mutex_unlock(&rdir->mutex);
195exit:
196	return err;
197}
198
199/**
200 * v9fs_dir_readdir_dotl - read a directory
201 * @filp: opened file structure
202 * @dirent: buffer to fill dirent structures
203 * @filldir: function to populate dirent structures
204 *
205 */
206static int v9fs_dir_readdir_dotl(struct file *filp, void *dirent,
207						filldir_t filldir)
208{
209	int over;
210	int err = 0;
211	struct p9_fid *fid;
212	int buflen;
213	struct p9_rdir *rdir;
214	struct p9_dirent curdirent;
215	u64 oldoffset = 0;
216
217	P9_DPRINTK(P9_DEBUG_VFS, "name %s\n", filp->f_path.dentry->d_name.name);
218	fid = filp->private_data;
219
220	buflen = fid->clnt->msize - P9_READDIRHDRSZ;
221
222	err = v9fs_alloc_rdir_buf(filp, buflen);
223	if (err)
224		goto exit;
225	rdir = (struct p9_rdir *) fid->rdir;
226
227	err = mutex_lock_interruptible(&rdir->mutex);
228	if (err)
229		return err;
230
231	while (err == 0) {
232		if (rdir->tail == rdir->head) {
233			err = p9_client_readdir(fid, rdir->buf, buflen,
234								filp->f_pos);
235			if (err <= 0)
236				goto unlock_and_exit;
237
238			rdir->head = 0;
239			rdir->tail = err;
240		}
241
242		while (rdir->head < rdir->tail) {
243
244			err = p9dirent_read(rdir->buf + rdir->head,
245						buflen - rdir->head, &curdirent,
246						fid->clnt->proto_version);
247			if (err < 0) {
248				P9_DPRINTK(P9_DEBUG_VFS, "returned %d\n", err);
249				err = -EIO;
250				goto unlock_and_exit;
251			}
252
253			/* d_off in dirent structure tracks the offset into
254			 * the next dirent in the dir. However, filldir()
255			 * expects offset into the current dirent. Hence
256			 * while calling filldir send the offset from the
257			 * previous dirent structure.
258			 */
259			over = filldir(dirent, curdirent.d_name,
260					strlen(curdirent.d_name),
261					oldoffset, v9fs_qid2ino(&curdirent.qid),
262					curdirent.d_type);
263			oldoffset = curdirent.d_off;
264
265			if (over) {
266				err = 0;
267				goto unlock_and_exit;
268			}
269
270			filp->f_pos = curdirent.d_off;
271			rdir->head += err;
272		}
273	}
274
275unlock_and_exit:
276	mutex_unlock(&rdir->mutex);
277exit:
278	return err;
279}
280
281
282/**
283 * v9fs_dir_release - close a directory
284 * @inode: inode of the directory
285 * @filp: file pointer to a directory
286 *
287 */
288
289int v9fs_dir_release(struct inode *inode, struct file *filp)
290{
291	struct p9_fid *fid;
292
293	fid = filp->private_data;
294	P9_DPRINTK(P9_DEBUG_VFS,
295			"v9fs_dir_release: inode: %p filp: %p fid: %d\n",
296			inode, filp, fid ? fid->fid : -1);
297	filemap_write_and_wait(inode->i_mapping);
298	if (fid)
299		p9_client_clunk(fid);
300	return 0;
301}
302
303const struct file_operations v9fs_dir_operations = {
304	.read = generic_read_dir,
305	.llseek = generic_file_llseek,
306	.readdir = v9fs_dir_readdir,
307	.open = v9fs_file_open,
308	.release = v9fs_dir_release,
309};
310
311const struct file_operations v9fs_dir_operations_dotl = {
312	.read = generic_read_dir,
313	.llseek = generic_file_llseek,
314	.readdir = v9fs_dir_readdir_dotl,
315	.open = v9fs_file_open,
316	.release = v9fs_dir_release,
317};
318