1/*
2 *  linux/fs/readdir.c
3 *
4 *  Copyright (C) 1995  Linus Torvalds
5 */
6
7#include <linux/stddef.h>
8#include <linux/kernel.h>
9#include <linux/module.h>
10#include <linux/time.h>
11#include <linux/mm.h>
12#include <linux/errno.h>
13#include <linux/stat.h>
14#include <linux/file.h>
15#include <linux/fs.h>
16#include <linux/dirent.h>
17#include <linux/security.h>
18#include <linux/syscalls.h>
19#include <linux/unistd.h>
20
21#include <asm/uaccess.h>
22
23int vfs_readdir(struct file *file, filldir_t filler, void *buf)
24{
25	struct inode *inode = file->f_path.dentry->d_inode;
26	int res = -ENOTDIR;
27	if (!file->f_op || !file->f_op->readdir)
28		goto out;
29
30	res = security_file_permission(file, MAY_READ);
31	if (res)
32		goto out;
33
34	res = mutex_lock_killable(&inode->i_mutex);
35	if (res)
36		goto out;
37
38	res = -ENOENT;
39	if (!IS_DEADDIR(inode)) {
40		res = file->f_op->readdir(file, buf, filler);
41		file_accessed(file);
42	}
43	mutex_unlock(&inode->i_mutex);
44out:
45	return res;
46}
47
48EXPORT_SYMBOL(vfs_readdir);
49
50/*
51 * Traditional linux readdir() handling..
52 *
53 * "count=1" is a special case, meaning that the buffer is one
54 * dirent-structure in size and that the code can't handle more
55 * anyway. Thus the special "fillonedir()" function for that
56 * case (the low-level handlers don't need to care about this).
57 */
58
59#ifdef __ARCH_WANT_OLD_READDIR
60
61struct old_linux_dirent {
62	unsigned long	d_ino;
63	unsigned long	d_offset;
64	unsigned short	d_namlen;
65	char		d_name[1];
66};
67
68struct readdir_callback {
69	struct old_linux_dirent __user * dirent;
70	int result;
71};
72
73static int fillonedir(void * __buf, const char * name, int namlen, loff_t offset,
74		      u64 ino, unsigned int d_type)
75{
76	struct readdir_callback * buf = (struct readdir_callback *) __buf;
77	struct old_linux_dirent __user * dirent;
78	unsigned long d_ino;
79
80	if (buf->result)
81		return -EINVAL;
82	d_ino = ino;
83	if (sizeof(d_ino) < sizeof(ino) && d_ino != ino) {
84		buf->result = -EOVERFLOW;
85		return -EOVERFLOW;
86	}
87	buf->result++;
88	dirent = buf->dirent;
89	if (!access_ok(VERIFY_WRITE, dirent,
90			(unsigned long)(dirent->d_name + namlen + 1) -
91				(unsigned long)dirent))
92		goto efault;
93	if (	__put_user(d_ino, &dirent->d_ino) ||
94		__put_user(offset, &dirent->d_offset) ||
95		__put_user(namlen, &dirent->d_namlen) ||
96		__copy_to_user(dirent->d_name, name, namlen) ||
97		__put_user(0, dirent->d_name + namlen))
98		goto efault;
99	return 0;
100efault:
101	buf->result = -EFAULT;
102	return -EFAULT;
103}
104
105SYSCALL_DEFINE3(old_readdir, unsigned int, fd,
106		struct old_linux_dirent __user *, dirent, unsigned int, count)
107{
108	int error;
109	struct file * file;
110	struct readdir_callback buf;
111
112	error = -EBADF;
113	file = fget(fd);
114	if (!file)
115		goto out;
116
117	buf.result = 0;
118	buf.dirent = dirent;
119
120	error = vfs_readdir(file, fillonedir, &buf);
121	if (buf.result)
122		error = buf.result;
123
124	fput(file);
125out:
126	return error;
127}
128
129#endif /* __ARCH_WANT_OLD_READDIR */
130
131/*
132 * New, all-improved, singing, dancing, iBCS2-compliant getdents()
133 * interface.
134 */
135struct linux_dirent {
136	unsigned long	d_ino;
137	unsigned long	d_off;
138	unsigned short	d_reclen;
139	char		d_name[1];
140};
141
142struct getdents_callback {
143	struct linux_dirent __user * current_dir;
144	struct linux_dirent __user * previous;
145	int count;
146	int error;
147};
148
149static int filldir(void * __buf, const char * name, int namlen, loff_t offset,
150		   u64 ino, unsigned int d_type)
151{
152	struct linux_dirent __user * dirent;
153	struct getdents_callback * buf = (struct getdents_callback *) __buf;
154	unsigned long d_ino;
155	int reclen = ALIGN(offsetof(struct linux_dirent, d_name) + namlen + 2,
156		sizeof(long));
157
158	buf->error = -EINVAL;	/* only used if we fail.. */
159	if (reclen > buf->count)
160		return -EINVAL;
161	d_ino = ino;
162	if (sizeof(d_ino) < sizeof(ino) && d_ino != ino) {
163		buf->error = -EOVERFLOW;
164		return -EOVERFLOW;
165	}
166	dirent = buf->previous;
167	if (dirent) {
168		if (__put_user(offset, &dirent->d_off))
169			goto efault;
170	}
171	dirent = buf->current_dir;
172	if (__put_user(d_ino, &dirent->d_ino))
173		goto efault;
174	if (__put_user(reclen, &dirent->d_reclen))
175		goto efault;
176	if (copy_to_user(dirent->d_name, name, namlen))
177		goto efault;
178	if (__put_user(0, dirent->d_name + namlen))
179		goto efault;
180	if (__put_user(d_type, (char __user *) dirent + reclen - 1))
181		goto efault;
182	buf->previous = dirent;
183	dirent = (void __user *)dirent + reclen;
184	buf->current_dir = dirent;
185	buf->count -= reclen;
186	return 0;
187efault:
188	buf->error = -EFAULT;
189	return -EFAULT;
190}
191
192SYSCALL_DEFINE3(getdents, unsigned int, fd,
193		struct linux_dirent __user *, dirent, unsigned int, count)
194{
195	struct file * file;
196	struct linux_dirent __user * lastdirent;
197	struct getdents_callback buf;
198	int error;
199
200	error = -EFAULT;
201	if (!access_ok(VERIFY_WRITE, dirent, count))
202		goto out;
203
204	error = -EBADF;
205	file = fget(fd);
206	if (!file)
207		goto out;
208
209	buf.current_dir = dirent;
210	buf.previous = NULL;
211	buf.count = count;
212	buf.error = 0;
213
214	error = vfs_readdir(file, filldir, &buf);
215	if (error >= 0)
216		error = buf.error;
217	lastdirent = buf.previous;
218	if (lastdirent) {
219		if (put_user(file->f_pos, &lastdirent->d_off))
220			error = -EFAULT;
221		else
222			error = count - buf.count;
223	}
224	fput(file);
225out:
226	return error;
227}
228
229struct getdents_callback64 {
230	struct linux_dirent64 __user * current_dir;
231	struct linux_dirent64 __user * previous;
232	int count;
233	int error;
234};
235
236static int filldir64(void * __buf, const char * name, int namlen, loff_t offset,
237		     u64 ino, unsigned int d_type)
238{
239	struct linux_dirent64 __user *dirent;
240	struct getdents_callback64 * buf = (struct getdents_callback64 *) __buf;
241	int reclen = ALIGN(offsetof(struct linux_dirent64, d_name) + namlen + 1,
242		sizeof(u64));
243
244	buf->error = -EINVAL;	/* only used if we fail.. */
245	if (reclen > buf->count)
246		return -EINVAL;
247	dirent = buf->previous;
248	if (dirent) {
249		if (__put_user(offset, &dirent->d_off))
250			goto efault;
251	}
252	dirent = buf->current_dir;
253	if (__put_user(ino, &dirent->d_ino))
254		goto efault;
255	if (__put_user(0, &dirent->d_off))
256		goto efault;
257	if (__put_user(reclen, &dirent->d_reclen))
258		goto efault;
259	if (__put_user(d_type, &dirent->d_type))
260		goto efault;
261	if (copy_to_user(dirent->d_name, name, namlen))
262		goto efault;
263	if (__put_user(0, dirent->d_name + namlen))
264		goto efault;
265	buf->previous = dirent;
266	dirent = (void __user *)dirent + reclen;
267	buf->current_dir = dirent;
268	buf->count -= reclen;
269	return 0;
270efault:
271	buf->error = -EFAULT;
272	return -EFAULT;
273}
274
275SYSCALL_DEFINE3(getdents64, unsigned int, fd,
276		struct linux_dirent64 __user *, dirent, unsigned int, count)
277{
278	struct file * file;
279	struct linux_dirent64 __user * lastdirent;
280	struct getdents_callback64 buf;
281	int error;
282
283	error = -EFAULT;
284	if (!access_ok(VERIFY_WRITE, dirent, count))
285		goto out;
286
287	error = -EBADF;
288	file = fget(fd);
289	if (!file)
290		goto out;
291
292	buf.current_dir = dirent;
293	buf.previous = NULL;
294	buf.count = count;
295	buf.error = 0;
296
297	error = vfs_readdir(file, filldir64, &buf);
298	if (error >= 0)
299		error = buf.error;
300	lastdirent = buf.previous;
301	if (lastdirent) {
302		typeof(lastdirent->d_off) d_off = file->f_pos;
303		if (__put_user(d_off, &lastdirent->d_off))
304			error = -EFAULT;
305		else
306			error = count - buf.count;
307	}
308	fput(file);
309out:
310	return error;
311}
312