Blame view

fs/readdir.c 11.2 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;
002354112   Al Viro   restore killabili...
38
  	if (shared) {
619226944   Al Viro   introduce a paral...
39
  		inode_lock_shared(inode);
002354112   Al Viro   restore killabili...
40
41
42
43
44
  	} else {
  		res = down_write_killable(&inode->i_rwsem);
  		if (res)
  			goto out;
  	}
da7845119   Liam R. Howlett   Use mutex_lock_ki...
45

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

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

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

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

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

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

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