Blame view

fs/readdir.c 13.6 KB
b24413180   Greg Kroah-Hartman   License cleanup: ...
1
  // SPDX-License-Identifier: GPL-2.0
1da177e4c   Linus Torvalds   Linux-2.6.12-rc2
2
3
4
5
6
  /*
   *  linux/fs/readdir.c
   *
   *  Copyright (C) 1995  Linus Torvalds
   */
85c9fe8fc   Kevin Winchester   vfs: fix warning:...
7
  #include <linux/stddef.h>
022a16924   Milind Arun Choudhary   ROUND_UP macro cl...
8
  #include <linux/kernel.h>
630d9c472   Paul Gortmaker   fs: reduce the us...
9
  #include <linux/export.h>
1da177e4c   Linus Torvalds   Linux-2.6.12-rc2
10
11
12
13
14
  #include <linux/time.h>
  #include <linux/mm.h>
  #include <linux/errno.h>
  #include <linux/stat.h>
  #include <linux/file.h>
1da177e4c   Linus Torvalds   Linux-2.6.12-rc2
15
  #include <linux/fs.h>
d4c7cf6cf   Heinrich Schuchardt   fanotify: create ...
16
  #include <linux/fsnotify.h>
1da177e4c   Linus Torvalds   Linux-2.6.12-rc2
17
18
19
20
  #include <linux/dirent.h>
  #include <linux/security.h>
  #include <linux/syscalls.h>
  #include <linux/unistd.h>
0460b2a28   Al Viro   readdir: move com...
21
  #include <linux/compat.h>
7c0f6ba68   Linus Torvalds   Replace <asm/uacc...
22
  #include <linux/uaccess.h>
1da177e4c   Linus Torvalds   Linux-2.6.12-rc2
23

9f79b78ef   Linus Torvalds   Convert filldir[6...
24
25
26
27
28
  #include <asm/unaligned.h>
  
  /*
   * Note the "unsafe_put_user() semantics: we goto a
   * label for errors.
9f79b78ef   Linus Torvalds   Convert filldir[6...
29
30
31
32
33
   */
  #define unsafe_copy_dirent_name(_dst, _src, _len, label) do {	\
  	char __user *dst = (_dst);				\
  	const char *src = (_src);				\
  	size_t len = (_len);					\
c512c6918   Linus Torvalds   uaccess: implemen...
34
35
  	unsafe_put_user(0, dst+len, label);			\
  	unsafe_copy_to_user(dst, src, len, label);		\
9f79b78ef   Linus Torvalds   Convert filldir[6...
36
  } while (0)
5c0ba4e07   Al Viro   [readdir] introdu...
37
  int iterate_dir(struct file *file, struct dir_context *ctx)
1da177e4c   Linus Torvalds   Linux-2.6.12-rc2
38
  {
496ad9aa8   Al Viro   new helper: file_...
39
  	struct inode *inode = file_inode(file);
619226944   Al Viro   introduce a paral...
40
  	bool shared = false;
1da177e4c   Linus Torvalds   Linux-2.6.12-rc2
41
  	int res = -ENOTDIR;
619226944   Al Viro   introduce a paral...
42
43
44
  	if (file->f_op->iterate_shared)
  		shared = true;
  	else if (!file->f_op->iterate)
1da177e4c   Linus Torvalds   Linux-2.6.12-rc2
45
46
47
48
49
  		goto out;
  
  	res = security_file_permission(file, MAY_READ);
  	if (res)
  		goto out;
0dc208b5d   Kirill Tkhai   locking/rwsem, fs...
50
51
52
  	if (shared)
  		res = down_read_killable(&inode->i_rwsem);
  	else
002354112   Al Viro   restore killabili...
53
  		res = down_write_killable(&inode->i_rwsem);
0dc208b5d   Kirill Tkhai   locking/rwsem, fs...
54
55
  	if (res)
  		goto out;
da7845119   Liam R. Howlett   Use mutex_lock_ki...
56

1da177e4c   Linus Torvalds   Linux-2.6.12-rc2
57
58
  	res = -ENOENT;
  	if (!IS_DEADDIR(inode)) {
2233f31aa   Al Viro   [readdir] ->readd...
59
  		ctx->pos = file->f_pos;
619226944   Al Viro   introduce a paral...
60
61
62
63
  		if (shared)
  			res = file->f_op->iterate_shared(file, ctx);
  		else
  			res = file->f_op->iterate(file, ctx);
2233f31aa   Al Viro   [readdir] ->readd...
64
  		file->f_pos = ctx->pos;
d4c7cf6cf   Heinrich Schuchardt   fanotify: create ...
65
  		fsnotify_access(file);
1da177e4c   Linus Torvalds   Linux-2.6.12-rc2
66
67
  		file_accessed(file);
  	}
619226944   Al Viro   introduce a paral...
68
69
70
71
  	if (shared)
  		inode_unlock_shared(inode);
  	else
  		inode_unlock(inode);
1da177e4c   Linus Torvalds   Linux-2.6.12-rc2
72
73
74
  out:
  	return res;
  }
5c0ba4e07   Al Viro   [readdir] introdu...
75
  EXPORT_SYMBOL(iterate_dir);
1da177e4c   Linus Torvalds   Linux-2.6.12-rc2
76
77
  
  /*
8a23eb804   Linus Torvalds   Make filldir[64](...
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
   * POSIX says that a dirent name cannot contain NULL or a '/'.
   *
   * It's not 100% clear what we should really do in this case.
   * The filesystem is clearly corrupted, but returning a hard
   * error means that you now don't see any of the other names
   * either, so that isn't a perfect alternative.
   *
   * And if you return an error, what error do you use? Several
   * filesystems seem to have decided on EUCLEAN being the error
   * code for EFSCORRUPTED, and that may be the error to use. Or
   * just EIO, which is perhaps more obvious to users.
   *
   * In order to see the other file names in the directory, the
   * caller might want to make this a "soft" error: skip the
   * entry, and return the error at the end instead.
   *
   * Note that this should likely do a "memchr(name, 0, len)"
   * check too, since that would be filesystem corruption as
   * well. However, that case can't actually confuse user space,
   * which has to do a strlen() on the name anyway to find the
   * filename length, and the above "soft error" worry means
   * that it's probably better left alone until we have that
   * issue clarified.
ab94448be   Linus Torvalds   readdir: be more ...
101
102
103
104
   *
   * Note the PATH_MAX check - it's arbitrary but the real
   * kernel limit on a possible path component, not NAME_MAX,
   * which is the technical standard limit.
8a23eb804   Linus Torvalds   Make filldir[64](...
105
106
107
   */
  static int verify_dirent_name(const char *name, int len)
  {
ab94448be   Linus Torvalds   readdir: be more ...
108
  	if (len <= 0 || len >= PATH_MAX)
8a23eb804   Linus Torvalds   Make filldir[64](...
109
  		return -EIO;
b9959c7a3   Linus Torvalds   filldir[64]: remo...
110
  	if (memchr(name, '/', len))
8a23eb804   Linus Torvalds   Make filldir[64](...
111
112
113
114
115
  		return -EIO;
  	return 0;
  }
  
  /*
1da177e4c   Linus Torvalds   Linux-2.6.12-rc2
116
117
118
119
120
121
122
   * 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).
   */
1da177e4c   Linus Torvalds   Linux-2.6.12-rc2
123
124
125
126
127
128
129
130
131
132
133
  
  #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 {
5c0ba4e07   Al Viro   [readdir] introdu...
134
  	struct dir_context ctx;
1da177e4c   Linus Torvalds   Linux-2.6.12-rc2
135
136
137
  	struct old_linux_dirent __user * dirent;
  	int result;
  };
ac7576f4b   Miklos Szeredi   vfs: make first a...
138
139
  static int fillonedir(struct dir_context *ctx, const char *name, int namlen,
  		      loff_t offset, u64 ino, unsigned int d_type)
1da177e4c   Linus Torvalds   Linux-2.6.12-rc2
140
  {
ac7576f4b   Miklos Szeredi   vfs: make first a...
141
142
  	struct readdir_callback *buf =
  		container_of(ctx, struct readdir_callback, ctx);
1da177e4c   Linus Torvalds   Linux-2.6.12-rc2
143
  	struct old_linux_dirent __user * dirent;
afefdbb28   David Howells   [PATCH] VFS: Make...
144
  	unsigned long d_ino;
1da177e4c   Linus Torvalds   Linux-2.6.12-rc2
145
146
147
  
  	if (buf->result)
  		return -EINVAL;
afefdbb28   David Howells   [PATCH] VFS: Make...
148
  	d_ino = ino;
8f3f655da   Al Viro   [PATCH] fix regul...
149
150
  	if (sizeof(d_ino) < sizeof(ino) && d_ino != ino) {
  		buf->result = -EOVERFLOW;
afefdbb28   David Howells   [PATCH] VFS: Make...
151
  		return -EOVERFLOW;
8f3f655da   Al Viro   [PATCH] fix regul...
152
  	}
1da177e4c   Linus Torvalds   Linux-2.6.12-rc2
153
154
  	buf->result++;
  	dirent = buf->dirent;
96d4f267e   Linus Torvalds   Remove 'type' arg...
155
  	if (!access_ok(dirent,
1da177e4c   Linus Torvalds   Linux-2.6.12-rc2
156
157
158
  			(unsigned long)(dirent->d_name + namlen + 1) -
  				(unsigned long)dirent))
  		goto efault;
afefdbb28   David Howells   [PATCH] VFS: Make...
159
  	if (	__put_user(d_ino, &dirent->d_ino) ||
1da177e4c   Linus Torvalds   Linux-2.6.12-rc2
160
161
162
163
164
165
166
167
168
169
  		__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;
  }
d4e82042c   Heiko Carstens   [CVE-2009-0029] S...
170
171
  SYSCALL_DEFINE3(old_readdir, unsigned int, fd,
  		struct old_linux_dirent __user *, dirent, unsigned int, count)
1da177e4c   Linus Torvalds   Linux-2.6.12-rc2
172
173
  {
  	int error;
63b6df141   Al Viro   give readdir(2)/g...
174
  	struct fd f = fdget_pos(fd);
ac6614b76   Al Viro   [readdir] constif...
175
176
177
178
  	struct readdir_callback buf = {
  		.ctx.actor = fillonedir,
  		.dirent = dirent
  	};
1da177e4c   Linus Torvalds   Linux-2.6.12-rc2
179

2903ff019   Al Viro   switch simple cas...
180
  	if (!f.file)
863ced7fe   Al Viro   switch readdir/ge...
181
  		return -EBADF;
1da177e4c   Linus Torvalds   Linux-2.6.12-rc2
182

5c0ba4e07   Al Viro   [readdir] introdu...
183
  	error = iterate_dir(f.file, &buf.ctx);
53c9c5c0e   Al Viro   [PATCH] prepare v...
184
  	if (buf.result)
1da177e4c   Linus Torvalds   Linux-2.6.12-rc2
185
  		error = buf.result;
63b6df141   Al Viro   give readdir(2)/g...
186
  	fdput_pos(f);
1da177e4c   Linus Torvalds   Linux-2.6.12-rc2
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
  	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 {
5c0ba4e07   Al Viro   [readdir] introdu...
204
  	struct dir_context ctx;
1da177e4c   Linus Torvalds   Linux-2.6.12-rc2
205
  	struct linux_dirent __user * current_dir;
376b86033   Linus Torvalds   readdir: make use...
206
  	int prev_reclen;
1da177e4c   Linus Torvalds   Linux-2.6.12-rc2
207
208
209
  	int count;
  	int error;
  };
ac7576f4b   Miklos Szeredi   vfs: make first a...
210
211
  static int filldir(struct dir_context *ctx, const char *name, int namlen,
  		   loff_t offset, u64 ino, unsigned int d_type)
1da177e4c   Linus Torvalds   Linux-2.6.12-rc2
212
  {
376b86033   Linus Torvalds   readdir: make use...
213
  	struct linux_dirent __user *dirent, *prev;
ac7576f4b   Miklos Szeredi   vfs: make first a...
214
215
  	struct getdents_callback *buf =
  		container_of(ctx, struct getdents_callback, ctx);
afefdbb28   David Howells   [PATCH] VFS: Make...
216
  	unsigned long d_ino;
85c9fe8fc   Kevin Winchester   vfs: fix warning:...
217
218
  	int reclen = ALIGN(offsetof(struct linux_dirent, d_name) + namlen + 2,
  		sizeof(long));
376b86033   Linus Torvalds   readdir: make use...
219
  	int prev_reclen;
1da177e4c   Linus Torvalds   Linux-2.6.12-rc2
220

8a23eb804   Linus Torvalds   Make filldir[64](...
221
222
223
  	buf->error = verify_dirent_name(name, namlen);
  	if (unlikely(buf->error))
  		return buf->error;
1da177e4c   Linus Torvalds   Linux-2.6.12-rc2
224
225
226
  	buf->error = -EINVAL;	/* only used if we fail.. */
  	if (reclen > buf->count)
  		return -EINVAL;
afefdbb28   David Howells   [PATCH] VFS: Make...
227
  	d_ino = ino;
8f3f655da   Al Viro   [PATCH] fix regul...
228
229
  	if (sizeof(d_ino) < sizeof(ino) && d_ino != ino) {
  		buf->error = -EOVERFLOW;
afefdbb28   David Howells   [PATCH] VFS: Make...
230
  		return -EOVERFLOW;
8f3f655da   Al Viro   [PATCH] fix regul...
231
  	}
376b86033   Linus Torvalds   readdir: make use...
232
233
  	prev_reclen = buf->prev_reclen;
  	if (prev_reclen && signal_pending(current))
9f79b78ef   Linus Torvalds   Convert filldir[6...
234
  		return -EINTR;
9f79b78ef   Linus Torvalds   Convert filldir[6...
235
  	dirent = buf->current_dir;
376b86033   Linus Torvalds   readdir: make use...
236
237
238
239
240
241
  	prev = (void __user *) dirent - prev_reclen;
  	if (!user_access_begin(prev, reclen + prev_reclen))
  		goto efault;
  
  	/* This might be 'dirent->d_off', but if so it will get overwritten */
  	unsafe_put_user(offset, &prev->d_off, efault_end);
9f79b78ef   Linus Torvalds   Convert filldir[6...
242
243
244
245
246
  	unsafe_put_user(d_ino, &dirent->d_ino, efault_end);
  	unsafe_put_user(reclen, &dirent->d_reclen, efault_end);
  	unsafe_put_user(d_type, (char __user *) dirent + reclen - 1, efault_end);
  	unsafe_copy_dirent_name(dirent->d_name, name, namlen, efault_end);
  	user_access_end();
376b86033   Linus Torvalds   readdir: make use...
247
248
  	buf->current_dir = (void __user *)dirent + reclen;
  	buf->prev_reclen = reclen;
1da177e4c   Linus Torvalds   Linux-2.6.12-rc2
249
250
  	buf->count -= reclen;
  	return 0;
9f79b78ef   Linus Torvalds   Convert filldir[6...
251
252
  efault_end:
  	user_access_end();
1da177e4c   Linus Torvalds   Linux-2.6.12-rc2
253
254
255
256
  efault:
  	buf->error = -EFAULT;
  	return -EFAULT;
  }
20f37034f   Heiko Carstens   [CVE-2009-0029] S...
257
258
  SYSCALL_DEFINE3(getdents, unsigned int, fd,
  		struct linux_dirent __user *, dirent, unsigned int, count)
1da177e4c   Linus Torvalds   Linux-2.6.12-rc2
259
  {
2903ff019   Al Viro   switch simple cas...
260
  	struct fd f;
ac6614b76   Al Viro   [readdir] constif...
261
262
263
264
265
  	struct getdents_callback buf = {
  		.ctx.actor = filldir,
  		.count = count,
  		.current_dir = dirent
  	};
1da177e4c   Linus Torvalds   Linux-2.6.12-rc2
266
  	int error;
96d4f267e   Linus Torvalds   Remove 'type' arg...
267
  	if (!access_ok(dirent, count))
863ced7fe   Al Viro   switch readdir/ge...
268
  		return -EFAULT;
1da177e4c   Linus Torvalds   Linux-2.6.12-rc2
269

63b6df141   Al Viro   give readdir(2)/g...
270
  	f = fdget_pos(fd);
2903ff019   Al Viro   switch simple cas...
271
  	if (!f.file)
863ced7fe   Al Viro   switch readdir/ge...
272
  		return -EBADF;
1da177e4c   Linus Torvalds   Linux-2.6.12-rc2
273

5c0ba4e07   Al Viro   [readdir] introdu...
274
  	error = iterate_dir(f.file, &buf.ctx);
53c9c5c0e   Al Viro   [PATCH] prepare v...
275
276
  	if (error >= 0)
  		error = buf.error;
376b86033   Linus Torvalds   readdir: make use...
277
278
279
  	if (buf.prev_reclen) {
  		struct linux_dirent __user * lastdirent;
  		lastdirent = (void __user *)buf.current_dir - buf.prev_reclen;
bb6f619b3   Al Viro   [readdir] introdu...
280
  		if (put_user(buf.ctx.pos, &lastdirent->d_off))
1da177e4c   Linus Torvalds   Linux-2.6.12-rc2
281
282
283
284
  			error = -EFAULT;
  		else
  			error = count - buf.count;
  	}
63b6df141   Al Viro   give readdir(2)/g...
285
  	fdput_pos(f);
1da177e4c   Linus Torvalds   Linux-2.6.12-rc2
286
287
  	return error;
  }
1da177e4c   Linus Torvalds   Linux-2.6.12-rc2
288
  struct getdents_callback64 {
5c0ba4e07   Al Viro   [readdir] introdu...
289
  	struct dir_context ctx;
1da177e4c   Linus Torvalds   Linux-2.6.12-rc2
290
  	struct linux_dirent64 __user * current_dir;
376b86033   Linus Torvalds   readdir: make use...
291
  	int prev_reclen;
1da177e4c   Linus Torvalds   Linux-2.6.12-rc2
292
293
294
  	int count;
  	int error;
  };
ac7576f4b   Miklos Szeredi   vfs: make first a...
295
296
  static int filldir64(struct dir_context *ctx, const char *name, int namlen,
  		     loff_t offset, u64 ino, unsigned int d_type)
1da177e4c   Linus Torvalds   Linux-2.6.12-rc2
297
  {
376b86033   Linus Torvalds   readdir: make use...
298
  	struct linux_dirent64 __user *dirent, *prev;
ac7576f4b   Miklos Szeredi   vfs: make first a...
299
300
  	struct getdents_callback64 *buf =
  		container_of(ctx, struct getdents_callback64, ctx);
85c9fe8fc   Kevin Winchester   vfs: fix warning:...
301
302
  	int reclen = ALIGN(offsetof(struct linux_dirent64, d_name) + namlen + 1,
  		sizeof(u64));
376b86033   Linus Torvalds   readdir: make use...
303
  	int prev_reclen;
1da177e4c   Linus Torvalds   Linux-2.6.12-rc2
304

8a23eb804   Linus Torvalds   Make filldir[64](...
305
306
307
  	buf->error = verify_dirent_name(name, namlen);
  	if (unlikely(buf->error))
  		return buf->error;
1da177e4c   Linus Torvalds   Linux-2.6.12-rc2
308
309
310
  	buf->error = -EINVAL;	/* only used if we fail.. */
  	if (reclen > buf->count)
  		return -EINVAL;
376b86033   Linus Torvalds   readdir: make use...
311
312
  	prev_reclen = buf->prev_reclen;
  	if (prev_reclen && signal_pending(current))
9f79b78ef   Linus Torvalds   Convert filldir[6...
313
  		return -EINTR;
9f79b78ef   Linus Torvalds   Convert filldir[6...
314
  	dirent = buf->current_dir;
376b86033   Linus Torvalds   readdir: make use...
315
316
317
318
319
320
  	prev = (void __user *)dirent - prev_reclen;
  	if (!user_access_begin(prev, reclen + prev_reclen))
  		goto efault;
  
  	/* This might be 'dirent->d_off', but if so it will get overwritten */
  	unsafe_put_user(offset, &prev->d_off, efault_end);
9f79b78ef   Linus Torvalds   Convert filldir[6...
321
322
323
324
325
  	unsafe_put_user(ino, &dirent->d_ino, efault_end);
  	unsafe_put_user(reclen, &dirent->d_reclen, efault_end);
  	unsafe_put_user(d_type, &dirent->d_type, efault_end);
  	unsafe_copy_dirent_name(dirent->d_name, name, namlen, efault_end);
  	user_access_end();
376b86033   Linus Torvalds   readdir: make use...
326
327
  	buf->prev_reclen = reclen;
  	buf->current_dir = (void __user *)dirent + reclen;
1da177e4c   Linus Torvalds   Linux-2.6.12-rc2
328
329
  	buf->count -= reclen;
  	return 0;
376b86033   Linus Torvalds   readdir: make use...
330

9f79b78ef   Linus Torvalds   Convert filldir[6...
331
332
  efault_end:
  	user_access_end();
1da177e4c   Linus Torvalds   Linux-2.6.12-rc2
333
334
335
336
  efault:
  	buf->error = -EFAULT;
  	return -EFAULT;
  }
454dab3f9   Dominik Brodowski   fs: add ksys_getd...
337
338
  int ksys_getdents64(unsigned int fd, struct linux_dirent64 __user *dirent,
  		    unsigned int count)
1da177e4c   Linus Torvalds   Linux-2.6.12-rc2
339
  {
2903ff019   Al Viro   switch simple cas...
340
  	struct fd f;
ac6614b76   Al Viro   [readdir] constif...
341
342
343
344
345
  	struct getdents_callback64 buf = {
  		.ctx.actor = filldir64,
  		.count = count,
  		.current_dir = dirent
  	};
1da177e4c   Linus Torvalds   Linux-2.6.12-rc2
346
  	int error;
96d4f267e   Linus Torvalds   Remove 'type' arg...
347
  	if (!access_ok(dirent, count))
863ced7fe   Al Viro   switch readdir/ge...
348
  		return -EFAULT;
1da177e4c   Linus Torvalds   Linux-2.6.12-rc2
349

63b6df141   Al Viro   give readdir(2)/g...
350
  	f = fdget_pos(fd);
2903ff019   Al Viro   switch simple cas...
351
  	if (!f.file)
863ced7fe   Al Viro   switch readdir/ge...
352
  		return -EBADF;
1da177e4c   Linus Torvalds   Linux-2.6.12-rc2
353

5c0ba4e07   Al Viro   [readdir] introdu...
354
  	error = iterate_dir(f.file, &buf.ctx);
53c9c5c0e   Al Viro   [PATCH] prepare v...
355
356
  	if (error >= 0)
  		error = buf.error;
376b86033   Linus Torvalds   readdir: make use...
357
358
  	if (buf.prev_reclen) {
  		struct linux_dirent64 __user * lastdirent;
bb6f619b3   Al Viro   [readdir] introdu...
359
  		typeof(lastdirent->d_off) d_off = buf.ctx.pos;
376b86033   Linus Torvalds   readdir: make use...
360
361
  
  		lastdirent = (void __user *) buf.current_dir - buf.prev_reclen;
1da177e4c   Linus Torvalds   Linux-2.6.12-rc2
362
  		if (__put_user(d_off, &lastdirent->d_off))
53c9c5c0e   Al Viro   [PATCH] prepare v...
363
364
365
  			error = -EFAULT;
  		else
  			error = count - buf.count;
1da177e4c   Linus Torvalds   Linux-2.6.12-rc2
366
  	}
63b6df141   Al Viro   give readdir(2)/g...
367
  	fdput_pos(f);
1da177e4c   Linus Torvalds   Linux-2.6.12-rc2
368
369
  	return error;
  }
0460b2a28   Al Viro   readdir: move com...
370

454dab3f9   Dominik Brodowski   fs: add ksys_getd...
371
372
373
374
375
376
  
  SYSCALL_DEFINE3(getdents64, unsigned int, fd,
  		struct linux_dirent64 __user *, dirent, unsigned int, count)
  {
  	return ksys_getdents64(fd, dirent, count);
  }
0460b2a28   Al Viro   readdir: move com...
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
  #ifdef CONFIG_COMPAT
  struct compat_old_linux_dirent {
  	compat_ulong_t	d_ino;
  	compat_ulong_t	d_offset;
  	unsigned short	d_namlen;
  	char		d_name[1];
  };
  
  struct compat_readdir_callback {
  	struct dir_context ctx;
  	struct compat_old_linux_dirent __user *dirent;
  	int result;
  };
  
  static int compat_fillonedir(struct dir_context *ctx, const char *name,
  			     int namlen, loff_t offset, u64 ino,
  			     unsigned int d_type)
  {
  	struct compat_readdir_callback *buf =
  		container_of(ctx, struct compat_readdir_callback, ctx);
  	struct compat_old_linux_dirent __user *dirent;
  	compat_ulong_t d_ino;
  
  	if (buf->result)
  		return -EINVAL;
  	d_ino = ino;
  	if (sizeof(d_ino) < sizeof(ino) && d_ino != ino) {
  		buf->result = -EOVERFLOW;
  		return -EOVERFLOW;
  	}
  	buf->result++;
  	dirent = buf->dirent;
96d4f267e   Linus Torvalds   Remove 'type' arg...
409
  	if (!access_ok(dirent,
0460b2a28   Al Viro   readdir: move com...
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
  			(unsigned long)(dirent->d_name + namlen + 1) -
  				(unsigned long)dirent))
  		goto efault;
  	if (	__put_user(d_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))
  		goto efault;
  	return 0;
  efault:
  	buf->result = -EFAULT;
  	return -EFAULT;
  }
  
  COMPAT_SYSCALL_DEFINE3(old_readdir, unsigned int, fd,
  		struct compat_old_linux_dirent __user *, dirent, unsigned int, count)
  {
  	int error;
  	struct fd f = fdget_pos(fd);
  	struct compat_readdir_callback buf = {
  		.ctx.actor = compat_fillonedir,
  		.dirent = dirent
  	};
  
  	if (!f.file)
  		return -EBADF;
  
  	error = iterate_dir(f.file, &buf.ctx);
  	if (buf.result)
  		error = buf.result;
  
  	fdput_pos(f);
  	return error;
  }
  
  struct compat_linux_dirent {
  	compat_ulong_t	d_ino;
  	compat_ulong_t	d_off;
  	unsigned short	d_reclen;
  	char		d_name[1];
  };
  
  struct compat_getdents_callback {
  	struct dir_context ctx;
  	struct compat_linux_dirent __user *current_dir;
  	struct compat_linux_dirent __user *previous;
  	int count;
  	int error;
  };
  
  static int compat_filldir(struct dir_context *ctx, const char *name, int namlen,
  		loff_t offset, u64 ino, unsigned int d_type)
  {
  	struct compat_linux_dirent __user * dirent;
  	struct compat_getdents_callback *buf =
  		container_of(ctx, struct compat_getdents_callback, ctx);
  	compat_ulong_t d_ino;
  	int reclen = ALIGN(offsetof(struct compat_linux_dirent, d_name) +
  		namlen + 2, sizeof(compat_long_t));
  
  	buf->error = -EINVAL;	/* only used if we fail.. */
  	if (reclen > buf->count)
  		return -EINVAL;
  	d_ino = ino;
  	if (sizeof(d_ino) < sizeof(ino) && d_ino != ino) {
  		buf->error = -EOVERFLOW;
  		return -EOVERFLOW;
  	}
  	dirent = buf->previous;
  	if (dirent) {
  		if (signal_pending(current))
  			return -EINTR;
  		if (__put_user(offset, &dirent->d_off))
  			goto efault;
  	}
  	dirent = buf->current_dir;
  	if (__put_user(d_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;
  	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;
  }
  
  COMPAT_SYSCALL_DEFINE3(getdents, unsigned int, fd,
  		struct compat_linux_dirent __user *, dirent, unsigned int, count)
  {
  	struct fd f;
  	struct compat_linux_dirent __user * lastdirent;
  	struct compat_getdents_callback buf = {
  		.ctx.actor = compat_filldir,
  		.current_dir = dirent,
  		.count = count
  	};
  	int error;
96d4f267e   Linus Torvalds   Remove 'type' arg...
518
  	if (!access_ok(dirent, count))
0460b2a28   Al Viro   readdir: move com...
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
  		return -EFAULT;
  
  	f = fdget_pos(fd);
  	if (!f.file)
  		return -EBADF;
  
  	error = iterate_dir(f.file, &buf.ctx);
  	if (error >= 0)
  		error = buf.error;
  	lastdirent = buf.previous;
  	if (lastdirent) {
  		if (put_user(buf.ctx.pos, &lastdirent->d_off))
  			error = -EFAULT;
  		else
  			error = count - buf.count;
  	}
  	fdput_pos(f);
  	return error;
  }
  #endif