1 /*
2  * Copyright (C) 2000-2006 Erik Andersen <andersen@uclibc.org>
3  *
4  * Licensed under the LGPL v2.1, see the file COPYING.LIB in this tarball.
5  */
6 
7 #include <_lfs_64.h>
8 #include <sys/syscall.h>
9 
10 #ifdef __NR_getdents64
11 
12 #include <assert.h>
13 #include <errno.h>
14 #include <dirent.h>
15 #include <stddef.h>
16 #include <stdint.h>
17 #include <string.h>
18 #include <unistd.h>
19 #include <sys/param.h>
20 #include <bits/wordsize.h>
21 #include <bits/uClibc_alloc.h>
22 
23 struct kernel_dirent64
24 {
25     uint64_t		d_ino;
26     int64_t		d_off;
27     unsigned short	d_reclen;
28     unsigned char	d_type;
29     char		d_name[256];
30 };
31 
32 # define __NR___syscall_getdents64 __NR_getdents64
_syscall3(int,__syscall_getdents64,int,fd,unsigned char *,dirp,size_t,count)33 static __inline__ _syscall3(int, __syscall_getdents64, int, fd, unsigned char *, dirp, size_t, count)
34 
35 ssize_t __getdents64 (int fd, char *buf, size_t nbytes)
36 {
37     struct dirent64 *dp;
38     off64_t last_offset = -1;
39     ssize_t retval;
40     size_t red_nbytes;
41     struct kernel_dirent64 *skdp, *kdp;
42     const size_t size_diff = (offsetof (struct dirent64, d_name)
43 	    - offsetof (struct kernel_dirent64, d_name));
44 
45     red_nbytes = MIN (nbytes - ((nbytes /
46 		    (offsetof (struct dirent64, d_name) + 14)) * size_diff),
47 	    nbytes - size_diff);
48 
49     dp = (struct dirent64 *) buf;
50     skdp = kdp = stack_heap_alloc(red_nbytes);
51 
52     retval = __syscall_getdents64(fd, (unsigned char *)kdp, red_nbytes);
53     if (retval == -1) {
54 	stack_heap_free(skdp);
55 	return -1;
56     }
57 
58     while ((char *) kdp < (char *) skdp + retval) {
59 	const size_t alignment = __alignof__ (struct dirent64);
60 	/* Since kdp->d_reclen is already aligned for the kernel structure
61 	   this may compute a value that is bigger than necessary.  */
62 	size_t new_reclen = ((kdp->d_reclen + size_diff + alignment - 1)
63 		& ~(alignment - 1));
64 	if ((char *) dp + new_reclen > buf + nbytes) {
65 	    /* Our heuristic failed.  We read too many entries.  Reset
66 	       the stream.  */
67 	    assert (last_offset != -1);
68 	    lseek64(fd, last_offset, SEEK_SET);
69 
70 	    if ((char *) dp == buf) {
71 		/* The buffer the user passed in is too small to hold even
72 		   one entry.  */
73 		stack_heap_free(skdp);
74 		__set_errno (EINVAL);
75 		return -1;
76 	    }
77 	    break;
78 	}
79 
80 	last_offset = kdp->d_off;
81 	dp->d_ino = kdp->d_ino;
82 	dp->d_off = kdp->d_off;
83 	dp->d_reclen = new_reclen;
84 	dp->d_type = kdp->d_type;
85 	memcpy (dp->d_name, kdp->d_name,
86 		kdp->d_reclen - offsetof (struct kernel_dirent64, d_name));
87 	dp = (struct dirent64 *) ((char *) dp + new_reclen);
88 	kdp = (struct kernel_dirent64 *) (((char *) kdp) + kdp->d_reclen);
89     }
90     stack_heap_free(skdp);
91     return (char *) dp - buf;
92 }
93 
94 #if __WORDSIZE == 64 || !defined __NR_getdents
95 /* since getdents doesnt give us d_type but getdents64 does, try and
96  * use getdents64 as much as possible */
97 strong_alias(__getdents64,__getdents)
98 #endif
99 
100 #endif
101