Blame view

fs/readdir.c 13.7 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.
2c6b7bcd7   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)
  {
2c6b7bcd7   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;
391b7461d   Al Viro   switch readdir(2)...
155
  	if (!user_write_access_begin(dirent,
1da177e4c   Linus Torvalds   Linux-2.6.12-rc2
156
157
158
  			(unsigned long)(dirent->d_name + namlen + 1) -
  				(unsigned long)dirent))
  		goto efault;
391b7461d   Al Viro   switch readdir(2)...
159
160
161
162
163
  	unsafe_put_user(d_ino, &dirent->d_ino, efault_end);
  	unsafe_put_user(offset, &dirent->d_offset, efault_end);
  	unsafe_put_user(namlen, &dirent->d_namlen, efault_end);
  	unsafe_copy_dirent_name(dirent->d_name, name, namlen, efault_end);
  	user_write_access_end();
1da177e4c   Linus Torvalds   Linux-2.6.12-rc2
164
  	return 0;
391b7461d   Al Viro   switch readdir(2)...
165
166
  efault_end:
  	user_write_access_end();
1da177e4c   Linus Torvalds   Linux-2.6.12-rc2
167
168
169
170
  efault:
  	buf->result = -EFAULT;
  	return -EFAULT;
  }
d4e82042c   Heiko Carstens   [CVE-2009-0029] S...
171
172
  SYSCALL_DEFINE3(old_readdir, unsigned int, fd,
  		struct old_linux_dirent __user *, dirent, unsigned int, count)
1da177e4c   Linus Torvalds   Linux-2.6.12-rc2
173
174
  {
  	int error;
63b6df141   Al Viro   give readdir(2)/g...
175
  	struct fd f = fdget_pos(fd);
ac6614b76   Al Viro   [readdir] constif...
176
177
178
179
  	struct readdir_callback buf = {
  		.ctx.actor = fillonedir,
  		.dirent = dirent
  	};
1da177e4c   Linus Torvalds   Linux-2.6.12-rc2
180

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

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

8a23eb804   Linus Torvalds   Make filldir[64](...
222
223
224
  	buf->error = verify_dirent_name(name, namlen);
  	if (unlikely(buf->error))
  		return buf->error;
1da177e4c   Linus Torvalds   Linux-2.6.12-rc2
225
226
227
  	buf->error = -EINVAL;	/* only used if we fail.. */
  	if (reclen > buf->count)
  		return -EINVAL;
afefdbb28   David Howells   [PATCH] VFS: Make...
228
  	d_ino = ino;
8f3f655da   Al Viro   [PATCH] fix regul...
229
230
  	if (sizeof(d_ino) < sizeof(ino) && d_ino != ino) {
  		buf->error = -EOVERFLOW;
afefdbb28   David Howells   [PATCH] VFS: Make...
231
  		return -EOVERFLOW;
8f3f655da   Al Viro   [PATCH] fix regul...
232
  	}
3c2659bd1   Linus Torvalds   readdir: make use...
233
234
  	prev_reclen = buf->prev_reclen;
  	if (prev_reclen && signal_pending(current))
9f79b78ef   Linus Torvalds   Convert filldir[6...
235
  		return -EINTR;
9f79b78ef   Linus Torvalds   Convert filldir[6...
236
  	dirent = buf->current_dir;
3c2659bd1   Linus Torvalds   readdir: make use...
237
  	prev = (void __user *) dirent - prev_reclen;
41cd78052   Christophe Leroy   uaccess: Selectiv...
238
  	if (!user_write_access_begin(prev, reclen + prev_reclen))
3c2659bd1   Linus Torvalds   readdir: make use...
239
240
241
242
  		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...
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);
41cd78052   Christophe Leroy   uaccess: Selectiv...
247
  	user_write_access_end();
9f79b78ef   Linus Torvalds   Convert filldir[6...
248

3c2659bd1   Linus Torvalds   readdir: make use...
249
250
  	buf->current_dir = (void __user *)dirent + reclen;
  	buf->prev_reclen = reclen;
1da177e4c   Linus Torvalds   Linux-2.6.12-rc2
251
252
  	buf->count -= reclen;
  	return 0;
9f79b78ef   Linus Torvalds   Convert filldir[6...
253
  efault_end:
41cd78052   Christophe Leroy   uaccess: Selectiv...
254
  	user_write_access_end();
1da177e4c   Linus Torvalds   Linux-2.6.12-rc2
255
256
257
258
  efault:
  	buf->error = -EFAULT;
  	return -EFAULT;
  }
20f37034f   Heiko Carstens   [CVE-2009-0029] S...
259
260
  SYSCALL_DEFINE3(getdents, unsigned int, fd,
  		struct linux_dirent __user *, dirent, unsigned int, count)
1da177e4c   Linus Torvalds   Linux-2.6.12-rc2
261
  {
2903ff019   Al Viro   switch simple cas...
262
  	struct fd f;
ac6614b76   Al Viro   [readdir] constif...
263
264
265
266
267
  	struct getdents_callback buf = {
  		.ctx.actor = filldir,
  		.count = count,
  		.current_dir = dirent
  	};
1da177e4c   Linus Torvalds   Linux-2.6.12-rc2
268
  	int error;
63b6df141   Al Viro   give readdir(2)/g...
269
  	f = fdget_pos(fd);
2903ff019   Al Viro   switch simple cas...
270
  	if (!f.file)
863ced7fe   Al Viro   switch readdir/ge...
271
  		return -EBADF;
1da177e4c   Linus Torvalds   Linux-2.6.12-rc2
272

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

8a23eb804   Linus Torvalds   Make filldir[64](...
304
305
306
  	buf->error = verify_dirent_name(name, namlen);
  	if (unlikely(buf->error))
  		return buf->error;
1da177e4c   Linus Torvalds   Linux-2.6.12-rc2
307
308
309
  	buf->error = -EINVAL;	/* only used if we fail.. */
  	if (reclen > buf->count)
  		return -EINVAL;
3c2659bd1   Linus Torvalds   readdir: make use...
310
311
  	prev_reclen = buf->prev_reclen;
  	if (prev_reclen && signal_pending(current))
9f79b78ef   Linus Torvalds   Convert filldir[6...
312
  		return -EINTR;
9f79b78ef   Linus Torvalds   Convert filldir[6...
313
  	dirent = buf->current_dir;
3c2659bd1   Linus Torvalds   readdir: make use...
314
  	prev = (void __user *)dirent - prev_reclen;
41cd78052   Christophe Leroy   uaccess: Selectiv...
315
  	if (!user_write_access_begin(prev, reclen + prev_reclen))
3c2659bd1   Linus Torvalds   readdir: make use...
316
317
318
319
  		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...
320
321
322
323
  	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);
41cd78052   Christophe Leroy   uaccess: Selectiv...
324
  	user_write_access_end();
9f79b78ef   Linus Torvalds   Convert filldir[6...
325

3c2659bd1   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;
3c2659bd1   Linus Torvalds   readdir: make use...
330

9f79b78ef   Linus Torvalds   Convert filldir[6...
331
  efault_end:
41cd78052   Christophe Leroy   uaccess: Selectiv...
332
  	user_write_access_end();
1da177e4c   Linus Torvalds   Linux-2.6.12-rc2
333
334
335
336
  efault:
  	buf->error = -EFAULT;
  	return -EFAULT;
  }
fb2da16cd   Christoph Hellwig   fs: remove ksys_g...
337
338
  SYSCALL_DEFINE3(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;
63b6df141   Al Viro   give readdir(2)/g...
347
  	f = fdget_pos(fd);
2903ff019   Al Viro   switch simple cas...
348
  	if (!f.file)
863ced7fe   Al Viro   switch readdir/ge...
349
  		return -EBADF;
1da177e4c   Linus Torvalds   Linux-2.6.12-rc2
350

5c0ba4e07   Al Viro   [readdir] introdu...
351
  	error = iterate_dir(f.file, &buf.ctx);
53c9c5c0e   Al Viro   [PATCH] prepare v...
352
353
  	if (error >= 0)
  		error = buf.error;
3c2659bd1   Linus Torvalds   readdir: make use...
354
355
  	if (buf.prev_reclen) {
  		struct linux_dirent64 __user * lastdirent;
bb6f619b3   Al Viro   [readdir] introdu...
356
  		typeof(lastdirent->d_off) d_off = buf.ctx.pos;
3c2659bd1   Linus Torvalds   readdir: make use...
357
358
  
  		lastdirent = (void __user *) buf.current_dir - buf.prev_reclen;
5fb151416   Al Viro   readdir.c: get ri...
359
  		if (put_user(d_off, &lastdirent->d_off))
53c9c5c0e   Al Viro   [PATCH] prepare v...
360
361
362
  			error = -EFAULT;
  		else
  			error = count - buf.count;
1da177e4c   Linus Torvalds   Linux-2.6.12-rc2
363
  	}
63b6df141   Al Viro   give readdir(2)/g...
364
  	fdput_pos(f);
1da177e4c   Linus Torvalds   Linux-2.6.12-rc2
365
366
  	return error;
  }
0460b2a28   Al Viro   readdir: move com...
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
  
  #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;
391b7461d   Al Viro   switch readdir(2)...
400
  	if (!user_write_access_begin(dirent,
0460b2a28   Al Viro   readdir: move com...
401
402
403
  			(unsigned long)(dirent->d_name + namlen + 1) -
  				(unsigned long)dirent))
  		goto efault;
391b7461d   Al Viro   switch readdir(2)...
404
405
406
407
408
  	unsafe_put_user(d_ino, &dirent->d_ino, efault_end);
  	unsafe_put_user(offset, &dirent->d_offset, efault_end);
  	unsafe_put_user(namlen, &dirent->d_namlen, efault_end);
  	unsafe_copy_dirent_name(dirent->d_name, name, namlen, efault_end);
  	user_write_access_end();
0460b2a28   Al Viro   readdir: move com...
409
  	return 0;
391b7461d   Al Viro   switch readdir(2)...
410
411
  efault_end:
  	user_write_access_end();
0460b2a28   Al Viro   readdir: move com...
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
  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;
82af599b7   Al Viro   readdir.c: get co...
448
  	int prev_reclen;
0460b2a28   Al Viro   readdir: move com...
449
450
451
452
453
454
455
  	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)
  {
82af599b7   Al Viro   readdir.c: get co...
456
  	struct compat_linux_dirent __user *dirent, *prev;
0460b2a28   Al Viro   readdir: move com...
457
458
459
460
461
  	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));
82af599b7   Al Viro   readdir.c: get co...
462
  	int prev_reclen;
0460b2a28   Al Viro   readdir: move com...
463

82af599b7   Al Viro   readdir.c: get co...
464
465
466
  	buf->error = verify_dirent_name(name, namlen);
  	if (unlikely(buf->error))
  		return buf->error;
0460b2a28   Al Viro   readdir: move com...
467
468
469
470
471
472
473
474
  	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;
  	}
82af599b7   Al Viro   readdir.c: get co...
475
476
477
  	prev_reclen = buf->prev_reclen;
  	if (prev_reclen && signal_pending(current))
  		return -EINTR;
0460b2a28   Al Viro   readdir: move com...
478
  	dirent = buf->current_dir;
82af599b7   Al Viro   readdir.c: get co...
479
480
  	prev = (void __user *) dirent - prev_reclen;
  	if (!user_write_access_begin(prev, reclen + prev_reclen))
0460b2a28   Al Viro   readdir: move com...
481
  		goto efault;
82af599b7   Al Viro   readdir.c: get co...
482
483
484
485
486
487
488
489
490
491
  
  	unsafe_put_user(offset, &prev->d_off, efault_end);
  	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_write_access_end();
  
  	buf->prev_reclen = reclen;
  	buf->current_dir = (void __user *)dirent + reclen;
0460b2a28   Al Viro   readdir: move com...
492
493
  	buf->count -= reclen;
  	return 0;
82af599b7   Al Viro   readdir.c: get co...
494
495
  efault_end:
  	user_write_access_end();
0460b2a28   Al Viro   readdir: move com...
496
497
498
499
500
501
502
503
504
  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;
0460b2a28   Al Viro   readdir: move com...
505
506
507
508
509
510
  	struct compat_getdents_callback buf = {
  		.ctx.actor = compat_filldir,
  		.current_dir = dirent,
  		.count = count
  	};
  	int error;
0460b2a28   Al Viro   readdir: move com...
511
512
513
514
515
516
517
  	f = fdget_pos(fd);
  	if (!f.file)
  		return -EBADF;
  
  	error = iterate_dir(f.file, &buf.ctx);
  	if (error >= 0)
  		error = buf.error;
82af599b7   Al Viro   readdir.c: get co...
518
519
520
  	if (buf.prev_reclen) {
  		struct compat_linux_dirent __user * lastdirent;
  		lastdirent = (void __user *)buf.current_dir - buf.prev_reclen;
0460b2a28   Al Viro   readdir: move com...
521
522
523
524
525
526
527
528
529
  		if (put_user(buf.ctx.pos, &lastdirent->d_off))
  			error = -EFAULT;
  		else
  			error = count - buf.count;
  	}
  	fdput_pos(f);
  	return error;
  }
  #endif