readdir.c 6.57 KB
Newer Older
Linus Torvalds's avatar
Linus Torvalds committed
1 2 3 4 5 6
/*
 *  linux/fs/readdir.c
 *
 *  Copyright (C) 1995  Linus Torvalds
 */

7
#include <linux/module.h>
8
#include <linux/time.h>
Linus Torvalds's avatar
Linus Torvalds committed
9 10 11 12 13
#include <linux/mm.h>
#include <linux/errno.h>
#include <linux/stat.h>
#include <linux/file.h>
#include <linux/smp_lock.h>
14
#include <linux/fs.h>
15
#include <linux/dirent.h>
16
#include <linux/security.h>
Linus Torvalds's avatar
Linus Torvalds committed
17 18 19 20 21 22 23 24 25

#include <asm/uaccess.h>

int vfs_readdir(struct file *file, filldir_t filler, void *buf)
{
	struct inode *inode = file->f_dentry->d_inode;
	int res = -ENOTDIR;
	if (!file->f_op || !file->f_op->readdir)
		goto out;
26

27 28
	res = security_file_permission(file, MAY_READ);
	if (res)
29 30
		goto out;

Linus Torvalds's avatar
Linus Torvalds committed
31 32 33 34
	down(&inode->i_sem);
	res = -ENOENT;
	if (!IS_DEADDIR(inode)) {
		res = file->f_op->readdir(file, buf, filler);
35
		file_accessed(file);
Linus Torvalds's avatar
Linus Torvalds committed
36 37 38 39 40 41
	}
	up(&inode->i_sem);
out:
	return res;
}

42 43
EXPORT_SYMBOL(vfs_readdir);

Linus Torvalds's avatar
Linus Torvalds committed
44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64
/*
 * Traditional linux readdir() handling..
 *
 * "count=1" is a special case, meaning that the buffer is one
 * dirent-structure in size and that the code can't handle more
 * anyway. Thus the special "fillonedir()" function for that
 * case (the low-level handlers don't need to care about this).
 */
#define NAME_OFFSET(de) ((int) ((de)->d_name - (char *) (de)))
#define ROUND_UP(x) (((x)+sizeof(long)-1) & ~(sizeof(long)-1))

#ifndef __ia64__

struct old_linux_dirent {
	unsigned long	d_ino;
	unsigned long	d_offset;
	unsigned short	d_namlen;
	char		d_name[1];
};

struct readdir_callback {
65
	struct old_linux_dirent __user * dirent;
66
	int result;
Linus Torvalds's avatar
Linus Torvalds committed
67 68
};

Linus Torvalds's avatar
Linus Torvalds committed
69
static int fillonedir(void * __buf, const char * name, int namlen, loff_t offset,
Linus Torvalds's avatar
Linus Torvalds committed
70 71 72
		      ino_t ino, unsigned int d_type)
{
	struct readdir_callback * buf = (struct readdir_callback *) __buf;
73
	struct old_linux_dirent __user * dirent;
Linus Torvalds's avatar
Linus Torvalds committed
74

75
	if (buf->result)
Linus Torvalds's avatar
Linus Torvalds committed
76
		return -EINVAL;
77
	buf->result++;
Linus Torvalds's avatar
Linus Torvalds committed
78
	dirent = buf->dirent;
79 80 81
	if (!access_ok(VERIFY_WRITE, (unsigned long)dirent,
			(unsigned long)(dirent->d_name + namlen + 1) -
				(unsigned long)dirent))
82
		goto efault;
83 84 85 86 87
	if (	__put_user(ino, &dirent->d_ino) ||
		__put_user(offset, &dirent->d_offset) ||
		__put_user(namlen, &dirent->d_namlen) ||
		__copy_to_user(dirent->d_name, name, namlen) ||
		__put_user(0, dirent->d_name + namlen))
88
		goto efault;
Linus Torvalds's avatar
Linus Torvalds committed
89
	return 0;
90 91 92
efault:
	buf->result = -EFAULT;
	return -EFAULT;
Linus Torvalds's avatar
Linus Torvalds committed
93 94
}

95
asmlinkage long old_readdir(unsigned int fd, struct old_linux_dirent __user * dirent, unsigned int count)
Linus Torvalds's avatar
Linus Torvalds committed
96 97 98 99 100 101 102 103 104 105
{
	int error;
	struct file * file;
	struct readdir_callback buf;

	error = -EBADF;
	file = fget(fd);
	if (!file)
		goto out;

106
	buf.result = 0;
Linus Torvalds's avatar
Linus Torvalds committed
107 108 109 110
	buf.dirent = dirent;

	error = vfs_readdir(file, fillonedir, &buf);
	if (error >= 0)
111
		error = buf.result;
Linus Torvalds's avatar
Linus Torvalds committed
112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131

	fput(file);
out:
	return error;
}

#endif /* !__ia64__ */

/*
 * New, all-improved, singing, dancing, iBCS2-compliant getdents()
 * interface. 
 */
struct linux_dirent {
	unsigned long	d_ino;
	unsigned long	d_off;
	unsigned short	d_reclen;
	char		d_name[1];
};

struct getdents_callback {
132 133
	struct linux_dirent __user * current_dir;
	struct linux_dirent __user * previous;
Linus Torvalds's avatar
Linus Torvalds committed
134 135 136 137
	int count;
	int error;
};

Linus Torvalds's avatar
Linus Torvalds committed
138
static int filldir(void * __buf, const char * name, int namlen, loff_t offset,
Linus Torvalds's avatar
Linus Torvalds committed
139 140
		   ino_t ino, unsigned int d_type)
{
141
	struct linux_dirent __user * dirent;
Linus Torvalds's avatar
Linus Torvalds committed
142
	struct getdents_callback * buf = (struct getdents_callback *) __buf;
143
	int reclen = ROUND_UP(NAME_OFFSET(dirent) + namlen + 2);
Linus Torvalds's avatar
Linus Torvalds committed
144 145 146 147 148

	buf->error = -EINVAL;	/* only used if we fail.. */
	if (reclen > buf->count)
		return -EINVAL;
	dirent = buf->previous;
149 150 151 152
	if (dirent) {
		if (__put_user(offset, &dirent->d_off))
			goto efault;
	}
Linus Torvalds's avatar
Linus Torvalds committed
153
	dirent = buf->current_dir;
154 155 156 157 158 159 160 161
	if (__put_user(ino, &dirent->d_ino))
		goto efault;
	if (__put_user(reclen, &dirent->d_reclen))
		goto efault;
	if (copy_to_user(dirent->d_name, name, namlen))
		goto efault;
	if (__put_user(0, dirent->d_name + namlen))
		goto efault;
162 163
	if (__put_user(d_type, (char *) dirent + reclen - 1))
		goto efault;
164
	buf->previous = dirent;
165
	dirent = (void __user *)dirent + reclen;
Linus Torvalds's avatar
Linus Torvalds committed
166 167 168
	buf->current_dir = dirent;
	buf->count -= reclen;
	return 0;
169
efault:
170
	buf->error = -EFAULT;
171
	return -EFAULT;
Linus Torvalds's avatar
Linus Torvalds committed
172 173
}

174
asmlinkage long sys_getdents(unsigned int fd, struct linux_dirent __user * dirent, unsigned int count)
Linus Torvalds's avatar
Linus Torvalds committed
175 176
{
	struct file * file;
177
	struct linux_dirent __user * lastdirent;
Linus Torvalds's avatar
Linus Torvalds committed
178 179 180
	struct getdents_callback buf;
	int error;

181 182 183 184
	error = -EFAULT;
	if (!access_ok(VERIFY_WRITE, dirent, count))
		goto out;

Linus Torvalds's avatar
Linus Torvalds committed
185 186 187 188 189
	error = -EBADF;
	file = fget(fd);
	if (!file)
		goto out;

190
	buf.current_dir = dirent;
Linus Torvalds's avatar
Linus Torvalds committed
191 192 193 194 195 196 197 198 199 200
	buf.previous = NULL;
	buf.count = count;
	buf.error = 0;

	error = vfs_readdir(file, filldir, &buf);
	if (error < 0)
		goto out_putf;
	error = buf.error;
	lastdirent = buf.previous;
	if (lastdirent) {
201 202 203 204
		if (put_user(file->f_pos, &lastdirent->d_off))
			error = -EFAULT;
		else
			error = count - buf.count;
Linus Torvalds's avatar
Linus Torvalds committed
205 206 207 208 209 210 211 212 213 214 215
	}

out_putf:
	fput(file);
out:
	return error;
}

#define ROUND_UP64(x) (((x)+sizeof(u64)-1) & ~(sizeof(u64)-1))

struct getdents_callback64 {
216 217
	struct linux_dirent64 __user * current_dir;
	struct linux_dirent64 __user * previous;
Linus Torvalds's avatar
Linus Torvalds committed
218 219 220 221
	int count;
	int error;
};

Linus Torvalds's avatar
Linus Torvalds committed
222
static int filldir64(void * __buf, const char * name, int namlen, loff_t offset,
Linus Torvalds's avatar
Linus Torvalds committed
223 224
		     ino_t ino, unsigned int d_type)
{
225
	struct linux_dirent64 __user *dirent;
Linus Torvalds's avatar
Linus Torvalds committed
226 227 228 229 230 231 232
	struct getdents_callback64 * buf = (struct getdents_callback64 *) __buf;
	int reclen = ROUND_UP64(NAME_OFFSET(dirent) + namlen + 1);

	buf->error = -EINVAL;	/* only used if we fail.. */
	if (reclen > buf->count)
		return -EINVAL;
	dirent = buf->previous;
233
	if (dirent) {
234 235
		if (__put_user(offset, &dirent->d_off))
			goto efault;
236
	}
Linus Torvalds's avatar
Linus Torvalds committed
237
	dirent = buf->current_dir;
238 239 240 241 242 243 244 245 246 247
	if (__put_user(ino, &dirent->d_ino))
		goto efault;
	if (__put_user(0, &dirent->d_off))
		goto efault;
	if (__put_user(reclen, &dirent->d_reclen))
		goto efault;
	if (__put_user(d_type, &dirent->d_type))
		goto efault;
	if (copy_to_user(dirent->d_name, name, namlen))
		goto efault;
248
	if (__put_user(0, dirent->d_name + namlen))
249
		goto efault;
250
	buf->previous = dirent;
251
	dirent = (void __user *)dirent + reclen;
Linus Torvalds's avatar
Linus Torvalds committed
252 253 254
	buf->current_dir = dirent;
	buf->count -= reclen;
	return 0;
255
efault:
256
	buf->error = -EFAULT;
257
	return -EFAULT;
Linus Torvalds's avatar
Linus Torvalds committed
258 259
}

260
asmlinkage long sys_getdents64(unsigned int fd, struct linux_dirent64 __user * dirent, unsigned int count)
Linus Torvalds's avatar
Linus Torvalds committed
261 262
{
	struct file * file;
263
	struct linux_dirent64 __user * lastdirent;
Linus Torvalds's avatar
Linus Torvalds committed
264 265 266
	struct getdents_callback64 buf;
	int error;

267
	error = -EFAULT;
268
	if (!access_ok(VERIFY_WRITE, dirent, count))
269 270
		goto out;

Linus Torvalds's avatar
Linus Torvalds committed
271 272 273 274 275
	error = -EBADF;
	file = fget(fd);
	if (!file)
		goto out;

276
	buf.current_dir = dirent;
Linus Torvalds's avatar
Linus Torvalds committed
277 278 279 280 281 282 283 284 285 286
	buf.previous = NULL;
	buf.count = count;
	buf.error = 0;

	error = vfs_readdir(file, filldir64, &buf);
	if (error < 0)
		goto out_putf;
	error = buf.error;
	lastdirent = buf.previous;
	if (lastdirent) {
287 288
		typeof(lastdirent->d_off) d_off = file->f_pos;
		__put_user(d_off, &lastdirent->d_off);
Linus Torvalds's avatar
Linus Torvalds committed
289 290 291 292 293 294 295 296
		error = count - buf.count;
	}

out_putf:
	fput(file);
out:
	return error;
}