readdir.c 6.94 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/stddef.h>
8
#include <linux/kernel.h>
9
#include <linux/export.h>
Linus Torvalds's avatar
Linus Torvalds committed
10 11 12 13 14 15
#include <linux/time.h>
#include <linux/mm.h>
#include <linux/errno.h>
#include <linux/stat.h>
#include <linux/file.h>
#include <linux/fs.h>
16
#include <linux/fsnotify.h>
Linus Torvalds's avatar
Linus Torvalds committed
17 18 19 20 21 22 23
#include <linux/dirent.h>
#include <linux/security.h>
#include <linux/syscalls.h>
#include <linux/unistd.h>

#include <asm/uaccess.h>

24
int iterate_dir(struct file *file, struct dir_context *ctx)
Linus Torvalds's avatar
Linus Torvalds committed
25
{
Al Viro's avatar
Al Viro committed
26
	struct inode *inode = file_inode(file);
Linus Torvalds's avatar
Linus Torvalds committed
27
	int res = -ENOTDIR;
Al Viro's avatar
Al Viro committed
28
	if (!file->f_op->iterate)
Linus Torvalds's avatar
Linus Torvalds committed
29 30 31 32 33 34
		goto out;

	res = security_file_permission(file, MAY_READ);
	if (res)
		goto out;

35 36 37 38
	res = mutex_lock_killable(&inode->i_mutex);
	if (res)
		goto out;

Linus Torvalds's avatar
Linus Torvalds committed
39 40
	res = -ENOENT;
	if (!IS_DEADDIR(inode)) {
Al Viro's avatar
Al Viro committed
41 42 43
		ctx->pos = file->f_pos;
		res = file->f_op->iterate(file, ctx);
		file->f_pos = ctx->pos;
44
		fsnotify_access(file);
Linus Torvalds's avatar
Linus Torvalds committed
45 46
		file_accessed(file);
	}
47
	mutex_unlock(&inode->i_mutex);
Linus Torvalds's avatar
Linus Torvalds committed
48 49 50
out:
	return res;
}
51
EXPORT_SYMBOL(iterate_dir);
Linus Torvalds's avatar
Linus Torvalds committed
52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71

/*
 * 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).
 */

#ifdef __ARCH_WANT_OLD_READDIR

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

struct readdir_callback {
72
	struct dir_context ctx;
Linus Torvalds's avatar
Linus Torvalds committed
73 74 75 76
	struct old_linux_dirent __user * dirent;
	int result;
};

77 78
static int fillonedir(struct dir_context *ctx, const char *name, int namlen,
		      loff_t offset, u64 ino, unsigned int d_type)
Linus Torvalds's avatar
Linus Torvalds committed
79
{
80 81
	struct readdir_callback *buf =
		container_of(ctx, struct readdir_callback, ctx);
Linus Torvalds's avatar
Linus Torvalds committed
82
	struct old_linux_dirent __user * dirent;
83
	unsigned long d_ino;
Linus Torvalds's avatar
Linus Torvalds committed
84 85 86

	if (buf->result)
		return -EINVAL;
87
	d_ino = ino;
88 89
	if (sizeof(d_ino) < sizeof(ino) && d_ino != ino) {
		buf->result = -EOVERFLOW;
90
		return -EOVERFLOW;
91
	}
Linus Torvalds's avatar
Linus Torvalds committed
92 93 94 95 96 97
	buf->result++;
	dirent = buf->dirent;
	if (!access_ok(VERIFY_WRITE, dirent,
			(unsigned long)(dirent->d_name + namlen + 1) -
				(unsigned long)dirent))
		goto efault;
98
	if (	__put_user(d_ino, &dirent->d_ino) ||
Linus Torvalds's avatar
Linus Torvalds committed
99 100 101 102 103 104 105 106 107 108 109
		__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))
		goto efault;
	return 0;
efault:
	buf->result = -EFAULT;
	return -EFAULT;
}

110 111
SYSCALL_DEFINE3(old_readdir, unsigned int, fd,
		struct old_linux_dirent __user *, dirent, unsigned int, count)
Linus Torvalds's avatar
Linus Torvalds committed
112 113
{
	int error;
114
	struct fd f = fdget(fd);
Al Viro's avatar
Al Viro committed
115 116 117 118
	struct readdir_callback buf = {
		.ctx.actor = fillonedir,
		.dirent = dirent
	};
Linus Torvalds's avatar
Linus Torvalds committed
119

120
	if (!f.file)
121
		return -EBADF;
Linus Torvalds's avatar
Linus Torvalds committed
122

123
	error = iterate_dir(f.file, &buf.ctx);
124
	if (buf.result)
Linus Torvalds's avatar
Linus Torvalds committed
125 126
		error = buf.result;

127
	fdput(f);
Linus Torvalds's avatar
Linus Torvalds committed
128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144
	return error;
}

#endif /* __ARCH_WANT_OLD_READDIR */

/*
 * 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 {
145
	struct dir_context ctx;
Linus Torvalds's avatar
Linus Torvalds committed
146 147 148 149 150 151
	struct linux_dirent __user * current_dir;
	struct linux_dirent __user * previous;
	int count;
	int error;
};

152 153
static int filldir(struct dir_context *ctx, const char *name, int namlen,
		   loff_t offset, u64 ino, unsigned int d_type)
Linus Torvalds's avatar
Linus Torvalds committed
154 155
{
	struct linux_dirent __user * dirent;
156 157
	struct getdents_callback *buf =
		container_of(ctx, struct getdents_callback, ctx);
158
	unsigned long d_ino;
159 160
	int reclen = ALIGN(offsetof(struct linux_dirent, d_name) + namlen + 2,
		sizeof(long));
Linus Torvalds's avatar
Linus Torvalds committed
161 162 163 164

	buf->error = -EINVAL;	/* only used if we fail.. */
	if (reclen > buf->count)
		return -EINVAL;
165
	d_ino = ino;
166 167
	if (sizeof(d_ino) < sizeof(ino) && d_ino != ino) {
		buf->error = -EOVERFLOW;
168
		return -EOVERFLOW;
169
	}
Linus Torvalds's avatar
Linus Torvalds committed
170 171 172 173 174 175
	dirent = buf->previous;
	if (dirent) {
		if (__put_user(offset, &dirent->d_off))
			goto efault;
	}
	dirent = buf->current_dir;
176
	if (__put_user(d_ino, &dirent->d_ino))
Linus Torvalds's avatar
Linus Torvalds committed
177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195
		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;
	if (__put_user(d_type, (char __user *) dirent + reclen - 1))
		goto efault;
	buf->previous = dirent;
	dirent = (void __user *)dirent + reclen;
	buf->current_dir = dirent;
	buf->count -= reclen;
	return 0;
efault:
	buf->error = -EFAULT;
	return -EFAULT;
}

196 197
SYSCALL_DEFINE3(getdents, unsigned int, fd,
		struct linux_dirent __user *, dirent, unsigned int, count)
Linus Torvalds's avatar
Linus Torvalds committed
198
{
199
	struct fd f;
Linus Torvalds's avatar
Linus Torvalds committed
200
	struct linux_dirent __user * lastdirent;
Al Viro's avatar
Al Viro committed
201 202 203 204 205
	struct getdents_callback buf = {
		.ctx.actor = filldir,
		.count = count,
		.current_dir = dirent
	};
Linus Torvalds's avatar
Linus Torvalds committed
206 207 208
	int error;

	if (!access_ok(VERIFY_WRITE, dirent, count))
209
		return -EFAULT;
Linus Torvalds's avatar
Linus Torvalds committed
210

211 212
	f = fdget(fd);
	if (!f.file)
213
		return -EBADF;
Linus Torvalds's avatar
Linus Torvalds committed
214

215
	error = iterate_dir(f.file, &buf.ctx);
216 217
	if (error >= 0)
		error = buf.error;
Linus Torvalds's avatar
Linus Torvalds committed
218 219
	lastdirent = buf.previous;
	if (lastdirent) {
220
		if (put_user(buf.ctx.pos, &lastdirent->d_off))
Linus Torvalds's avatar
Linus Torvalds committed
221 222 223 224
			error = -EFAULT;
		else
			error = count - buf.count;
	}
225
	fdput(f);
Linus Torvalds's avatar
Linus Torvalds committed
226 227 228 229
	return error;
}

struct getdents_callback64 {
230
	struct dir_context ctx;
Linus Torvalds's avatar
Linus Torvalds committed
231 232 233 234 235 236
	struct linux_dirent64 __user * current_dir;
	struct linux_dirent64 __user * previous;
	int count;
	int error;
};

237 238
static int filldir64(struct dir_context *ctx, const char *name, int namlen,
		     loff_t offset, u64 ino, unsigned int d_type)
Linus Torvalds's avatar
Linus Torvalds committed
239 240
{
	struct linux_dirent64 __user *dirent;
241 242
	struct getdents_callback64 *buf =
		container_of(ctx, struct getdents_callback64, ctx);
243 244
	int reclen = ALIGN(offsetof(struct linux_dirent64, d_name) + namlen + 1,
		sizeof(u64));
Linus Torvalds's avatar
Linus Torvalds committed
245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276

	buf->error = -EINVAL;	/* only used if we fail.. */
	if (reclen > buf->count)
		return -EINVAL;
	dirent = buf->previous;
	if (dirent) {
		if (__put_user(offset, &dirent->d_off))
			goto efault;
	}
	dirent = buf->current_dir;
	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;
	if (__put_user(0, dirent->d_name + namlen))
		goto efault;
	buf->previous = dirent;
	dirent = (void __user *)dirent + reclen;
	buf->current_dir = dirent;
	buf->count -= reclen;
	return 0;
efault:
	buf->error = -EFAULT;
	return -EFAULT;
}

277 278
SYSCALL_DEFINE3(getdents64, unsigned int, fd,
		struct linux_dirent64 __user *, dirent, unsigned int, count)
Linus Torvalds's avatar
Linus Torvalds committed
279
{
280
	struct fd f;
Linus Torvalds's avatar
Linus Torvalds committed
281
	struct linux_dirent64 __user * lastdirent;
Al Viro's avatar
Al Viro committed
282 283 284 285 286
	struct getdents_callback64 buf = {
		.ctx.actor = filldir64,
		.count = count,
		.current_dir = dirent
	};
Linus Torvalds's avatar
Linus Torvalds committed
287 288 289
	int error;

	if (!access_ok(VERIFY_WRITE, dirent, count))
290
		return -EFAULT;
Linus Torvalds's avatar
Linus Torvalds committed
291

292 293
	f = fdget(fd);
	if (!f.file)
294
		return -EBADF;
Linus Torvalds's avatar
Linus Torvalds committed
295

296
	error = iterate_dir(f.file, &buf.ctx);
297 298
	if (error >= 0)
		error = buf.error;
Linus Torvalds's avatar
Linus Torvalds committed
299 300
	lastdirent = buf.previous;
	if (lastdirent) {
301
		typeof(lastdirent->d_off) d_off = buf.ctx.pos;
Linus Torvalds's avatar
Linus Torvalds committed
302
		if (__put_user(d_off, &lastdirent->d_off))
303 304 305
			error = -EFAULT;
		else
			error = count - buf.count;
Linus Torvalds's avatar
Linus Torvalds committed
306
	}
307
	fdput(f);
Linus Torvalds's avatar
Linus Torvalds committed
308 309
	return error;
}