Blame view

fs/readdir.c 11.4 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>
1da177e4c   Linus Torvalds   Linux-2.6.12-rc2
22

7c0f6ba68   Linus Torvalds   Replace <asm/uacc...
23
  #include <linux/uaccess.h>
1da177e4c   Linus Torvalds   Linux-2.6.12-rc2
24

5c0ba4e07   Al Viro   [readdir] introdu...
25
  int iterate_dir(struct file *file, struct dir_context *ctx)
1da177e4c   Linus Torvalds   Linux-2.6.12-rc2
26
  {
496ad9aa8   Al Viro   new helper: file_...
27
  	struct inode *inode = file_inode(file);
619226944   Al Viro   introduce a paral...
28
  	bool shared = false;
1da177e4c   Linus Torvalds   Linux-2.6.12-rc2
29
  	int res = -ENOTDIR;
619226944   Al Viro   introduce a paral...
30
31
32
  	if (file->f_op->iterate_shared)
  		shared = true;
  	else if (!file->f_op->iterate)
1da177e4c   Linus Torvalds   Linux-2.6.12-rc2
33
34
35
36
37
  		goto out;
  
  	res = security_file_permission(file, MAY_READ);
  	if (res)
  		goto out;
0dc208b5d   Kirill Tkhai   locking/rwsem, fs...
38
39
40
  	if (shared)
  		res = down_read_killable(&inode->i_rwsem);
  	else
002354112   Al Viro   restore killabili...
41
  		res = down_write_killable(&inode->i_rwsem);
0dc208b5d   Kirill Tkhai   locking/rwsem, fs...
42
43
  	if (res)
  		goto out;
da7845119   Liam R. Howlett   Use mutex_lock_ki...
44

1da177e4c   Linus Torvalds   Linux-2.6.12-rc2
45
46
  	res = -ENOENT;
  	if (!IS_DEADDIR(inode)) {
2233f31aa   Al Viro   [readdir] ->readd...
47
  		ctx->pos = file->f_pos;
619226944   Al Viro   introduce a paral...
48
49
50
51
  		if (shared)
  			res = file->f_op->iterate_shared(file, ctx);
  		else
  			res = file->f_op->iterate(file, ctx);
2233f31aa   Al Viro   [readdir] ->readd...
52
  		file->f_pos = ctx->pos;
d4c7cf6cf   Heinrich Schuchardt   fanotify: create ...
53
  		fsnotify_access(file);
1da177e4c   Linus Torvalds   Linux-2.6.12-rc2
54
55
  		file_accessed(file);
  	}
619226944   Al Viro   introduce a paral...
56
57
58
59
  	if (shared)
  		inode_unlock_shared(inode);
  	else
  		inode_unlock(inode);
1da177e4c   Linus Torvalds   Linux-2.6.12-rc2
60
61
62
  out:
  	return res;
  }
5c0ba4e07   Al Viro   [readdir] introdu...
63
  EXPORT_SYMBOL(iterate_dir);
1da177e4c   Linus Torvalds   Linux-2.6.12-rc2
64
65
66
67
68
69
70
71
72
  
  /*
   * 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
73
74
75
76
77
78
79
80
81
82
83
  
  #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...
84
  	struct dir_context ctx;
1da177e4c   Linus Torvalds   Linux-2.6.12-rc2
85
86
87
  	struct old_linux_dirent __user * dirent;
  	int result;
  };
ac7576f4b   Miklos Szeredi   vfs: make first a...
88
89
  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
90
  {
ac7576f4b   Miklos Szeredi   vfs: make first a...
91
92
  	struct readdir_callback *buf =
  		container_of(ctx, struct readdir_callback, ctx);
1da177e4c   Linus Torvalds   Linux-2.6.12-rc2
93
  	struct old_linux_dirent __user * dirent;
afefdbb28   David Howells   [PATCH] VFS: Make...
94
  	unsigned long d_ino;
1da177e4c   Linus Torvalds   Linux-2.6.12-rc2
95
96
97
  
  	if (buf->result)
  		return -EINVAL;
afefdbb28   David Howells   [PATCH] VFS: Make...
98
  	d_ino = ino;
8f3f655da   Al Viro   [PATCH] fix regul...
99
100
  	if (sizeof(d_ino) < sizeof(ino) && d_ino != ino) {
  		buf->result = -EOVERFLOW;
afefdbb28   David Howells   [PATCH] VFS: Make...
101
  		return -EOVERFLOW;
8f3f655da   Al Viro   [PATCH] fix regul...
102
  	}
1da177e4c   Linus Torvalds   Linux-2.6.12-rc2
103
104
105
106
107
108
  	buf->result++;
  	dirent = buf->dirent;
  	if (!access_ok(VERIFY_WRITE, dirent,
  			(unsigned long)(dirent->d_name + namlen + 1) -
  				(unsigned long)dirent))
  		goto efault;
afefdbb28   David Howells   [PATCH] VFS: Make...
109
  	if (	__put_user(d_ino, &dirent->d_ino) ||
1da177e4c   Linus Torvalds   Linux-2.6.12-rc2
110
111
112
113
114
115
116
117
118
119
  		__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...
120
121
  SYSCALL_DEFINE3(old_readdir, unsigned int, fd,
  		struct old_linux_dirent __user *, dirent, unsigned int, count)
1da177e4c   Linus Torvalds   Linux-2.6.12-rc2
122
123
  {
  	int error;
63b6df141   Al Viro   give readdir(2)/g...
124
  	struct fd f = fdget_pos(fd);
ac6614b76   Al Viro   [readdir] constif...
125
126
127
128
  	struct readdir_callback buf = {
  		.ctx.actor = fillonedir,
  		.dirent = dirent
  	};
1da177e4c   Linus Torvalds   Linux-2.6.12-rc2
129

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

5c0ba4e07   Al Viro   [readdir] introdu...
133
  	error = iterate_dir(f.file, &buf.ctx);
53c9c5c0e   Al Viro   [PATCH] prepare v...
134
  	if (buf.result)
1da177e4c   Linus Torvalds   Linux-2.6.12-rc2
135
  		error = buf.result;
63b6df141   Al Viro   give readdir(2)/g...
136
  	fdput_pos(f);
1da177e4c   Linus Torvalds   Linux-2.6.12-rc2
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
  	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...
154
  	struct dir_context ctx;
1da177e4c   Linus Torvalds   Linux-2.6.12-rc2
155
156
157
158
159
  	struct linux_dirent __user * current_dir;
  	struct linux_dirent __user * previous;
  	int count;
  	int error;
  };
ac7576f4b   Miklos Szeredi   vfs: make first a...
160
161
  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
162
163
  {
  	struct linux_dirent __user * dirent;
ac7576f4b   Miklos Szeredi   vfs: make first a...
164
165
  	struct getdents_callback *buf =
  		container_of(ctx, struct getdents_callback, ctx);
afefdbb28   David Howells   [PATCH] VFS: Make...
166
  	unsigned long d_ino;
85c9fe8fc   Kevin Winchester   vfs: fix warning:...
167
168
  	int reclen = ALIGN(offsetof(struct linux_dirent, d_name) + namlen + 2,
  		sizeof(long));
1da177e4c   Linus Torvalds   Linux-2.6.12-rc2
169
170
171
172
  
  	buf->error = -EINVAL;	/* only used if we fail.. */
  	if (reclen > buf->count)
  		return -EINVAL;
afefdbb28   David Howells   [PATCH] VFS: Make...
173
  	d_ino = ino;
8f3f655da   Al Viro   [PATCH] fix regul...
174
175
  	if (sizeof(d_ino) < sizeof(ino) && d_ino != ino) {
  		buf->error = -EOVERFLOW;
afefdbb28   David Howells   [PATCH] VFS: Make...
176
  		return -EOVERFLOW;
8f3f655da   Al Viro   [PATCH] fix regul...
177
  	}
1da177e4c   Linus Torvalds   Linux-2.6.12-rc2
178
179
  	dirent = buf->previous;
  	if (dirent) {
1f60fbe72   Theodore Ts'o   ext4: allow readd...
180
181
  		if (signal_pending(current))
  			return -EINTR;
1da177e4c   Linus Torvalds   Linux-2.6.12-rc2
182
183
184
185
  		if (__put_user(offset, &dirent->d_off))
  			goto efault;
  	}
  	dirent = buf->current_dir;
afefdbb28   David Howells   [PATCH] VFS: Make...
186
  	if (__put_user(d_ino, &dirent->d_ino))
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
204
  		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;
  }
20f37034f   Heiko Carstens   [CVE-2009-0029] S...
205
206
  SYSCALL_DEFINE3(getdents, unsigned int, fd,
  		struct linux_dirent __user *, dirent, unsigned int, count)
1da177e4c   Linus Torvalds   Linux-2.6.12-rc2
207
  {
2903ff019   Al Viro   switch simple cas...
208
  	struct fd f;
1da177e4c   Linus Torvalds   Linux-2.6.12-rc2
209
  	struct linux_dirent __user * lastdirent;
ac6614b76   Al Viro   [readdir] constif...
210
211
212
213
214
  	struct getdents_callback buf = {
  		.ctx.actor = filldir,
  		.count = count,
  		.current_dir = dirent
  	};
1da177e4c   Linus Torvalds   Linux-2.6.12-rc2
215
  	int error;
1da177e4c   Linus Torvalds   Linux-2.6.12-rc2
216
  	if (!access_ok(VERIFY_WRITE, dirent, count))
863ced7fe   Al Viro   switch readdir/ge...
217
  		return -EFAULT;
1da177e4c   Linus Torvalds   Linux-2.6.12-rc2
218

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

5c0ba4e07   Al Viro   [readdir] introdu...
223
  	error = iterate_dir(f.file, &buf.ctx);
53c9c5c0e   Al Viro   [PATCH] prepare v...
224
225
  	if (error >= 0)
  		error = buf.error;
1da177e4c   Linus Torvalds   Linux-2.6.12-rc2
226
227
  	lastdirent = buf.previous;
  	if (lastdirent) {
bb6f619b3   Al Viro   [readdir] introdu...
228
  		if (put_user(buf.ctx.pos, &lastdirent->d_off))
1da177e4c   Linus Torvalds   Linux-2.6.12-rc2
229
230
231
232
  			error = -EFAULT;
  		else
  			error = count - buf.count;
  	}
63b6df141   Al Viro   give readdir(2)/g...
233
  	fdput_pos(f);
1da177e4c   Linus Torvalds   Linux-2.6.12-rc2
234
235
  	return error;
  }
1da177e4c   Linus Torvalds   Linux-2.6.12-rc2
236
  struct getdents_callback64 {
5c0ba4e07   Al Viro   [readdir] introdu...
237
  	struct dir_context ctx;
1da177e4c   Linus Torvalds   Linux-2.6.12-rc2
238
239
240
241
242
  	struct linux_dirent64 __user * current_dir;
  	struct linux_dirent64 __user * previous;
  	int count;
  	int error;
  };
ac7576f4b   Miklos Szeredi   vfs: make first a...
243
244
  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
245
246
  {
  	struct linux_dirent64 __user *dirent;
ac7576f4b   Miklos Szeredi   vfs: make first a...
247
248
  	struct getdents_callback64 *buf =
  		container_of(ctx, struct getdents_callback64, ctx);
85c9fe8fc   Kevin Winchester   vfs: fix warning:...
249
250
  	int reclen = ALIGN(offsetof(struct linux_dirent64, d_name) + namlen + 1,
  		sizeof(u64));
1da177e4c   Linus Torvalds   Linux-2.6.12-rc2
251
252
253
254
255
256
  
  	buf->error = -EINVAL;	/* only used if we fail.. */
  	if (reclen > buf->count)
  		return -EINVAL;
  	dirent = buf->previous;
  	if (dirent) {
1f60fbe72   Theodore Ts'o   ext4: allow readd...
257
258
  		if (signal_pending(current))
  			return -EINTR;
1da177e4c   Linus Torvalds   Linux-2.6.12-rc2
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
  		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;
  }
454dab3f9   Dominik Brodowski   fs: add ksys_getd...
284
285
  int ksys_getdents64(unsigned int fd, struct linux_dirent64 __user *dirent,
  		    unsigned int count)
1da177e4c   Linus Torvalds   Linux-2.6.12-rc2
286
  {
2903ff019   Al Viro   switch simple cas...
287
  	struct fd f;
1da177e4c   Linus Torvalds   Linux-2.6.12-rc2
288
  	struct linux_dirent64 __user * lastdirent;
ac6614b76   Al Viro   [readdir] constif...
289
290
291
292
293
  	struct getdents_callback64 buf = {
  		.ctx.actor = filldir64,
  		.count = count,
  		.current_dir = dirent
  	};
1da177e4c   Linus Torvalds   Linux-2.6.12-rc2
294
  	int error;
1da177e4c   Linus Torvalds   Linux-2.6.12-rc2
295
  	if (!access_ok(VERIFY_WRITE, dirent, count))
863ced7fe   Al Viro   switch readdir/ge...
296
  		return -EFAULT;
1da177e4c   Linus Torvalds   Linux-2.6.12-rc2
297

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

5c0ba4e07   Al Viro   [readdir] introdu...
302
  	error = iterate_dir(f.file, &buf.ctx);
53c9c5c0e   Al Viro   [PATCH] prepare v...
303
304
  	if (error >= 0)
  		error = buf.error;
1da177e4c   Linus Torvalds   Linux-2.6.12-rc2
305
306
  	lastdirent = buf.previous;
  	if (lastdirent) {
bb6f619b3   Al Viro   [readdir] introdu...
307
  		typeof(lastdirent->d_off) d_off = buf.ctx.pos;
1da177e4c   Linus Torvalds   Linux-2.6.12-rc2
308
  		if (__put_user(d_off, &lastdirent->d_off))
53c9c5c0e   Al Viro   [PATCH] prepare v...
309
310
311
  			error = -EFAULT;
  		else
  			error = count - buf.count;
1da177e4c   Linus Torvalds   Linux-2.6.12-rc2
312
  	}
63b6df141   Al Viro   give readdir(2)/g...
313
  	fdput_pos(f);
1da177e4c   Linus Torvalds   Linux-2.6.12-rc2
314
315
  	return error;
  }
0460b2a28   Al Viro   readdir: move com...
316

454dab3f9   Dominik Brodowski   fs: add ksys_getd...
317
318
319
320
321
322
  
  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...
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
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
400
401
402
403
404
405
406
407
408
409
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
  #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;
  	if (!access_ok(VERIFY_WRITE, dirent,
  			(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;
  
  	if (!access_ok(VERIFY_WRITE, dirent, count))
  		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