diff options
author | Eric Andersen <andersen@codepoet.org> | 2003-01-28 11:06:03 +0000 |
---|---|---|
committer | Eric Andersen <andersen@codepoet.org> | 2003-01-28 11:06:03 +0000 |
commit | 1e78eb0bd44caf57a234c49ff9b5193a977b6ad3 (patch) | |
tree | 5f48d2c9aeedfe8c04d989230427204f10f7e0ba /libc/sysdeps/linux/common/getdents64.c | |
parent | 46de710e624b2e578d9adf3bf44de5513867fba3 (diff) |
Fix scandir64 to not free the wrong pieces of memory (which could
and did cause segfaults) by adjusting the working scandir.c to
the the 64 thing. Fix up potential for mismatches between the
libc and kernel dirent structures, which could also cause ugly
problems.
-Erik
Diffstat (limited to 'libc/sysdeps/linux/common/getdents64.c')
-rw-r--r-- | libc/sysdeps/linux/common/getdents64.c | 108 |
1 files changed, 108 insertions, 0 deletions
diff --git a/libc/sysdeps/linux/common/getdents64.c b/libc/sysdeps/linux/common/getdents64.c new file mode 100644 index 000000000..0fb7336f3 --- /dev/null +++ b/libc/sysdeps/linux/common/getdents64.c @@ -0,0 +1,108 @@ +/* Copyright (C) 1993, 1995-2002 Free Software Foundation, Inc. + This file is part of the GNU C Library. + + The GNU C Library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) any later version. + + The GNU C Library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with the GNU C Library; if not, write to the Free + Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA + 02111-1307 USA. */ + +#include <features.h> + +#ifdef __UCLIBC_HAS_LFS__ + +#include <alloca.h> +#include <assert.h> +#include <errno.h> +#include <dirent.h> +#include <stddef.h> +#include <stdint.h> +#include <string.h> +#include <unistd.h> +#include <sys/param.h> +#include <sys/types.h> +#include <sysdep.h> +#include <sys/syscall.h> + + +#define offsetof(TYPE, MEMBER) ((size_t) &((TYPE *)0)->MEMBER) + +struct kernel_dirent64 +{ + uint64_t d_ino; + int64_t d_off; + unsigned short int d_reclen; + unsigned char d_type; + char d_name[256]; +}; + + +#define __NR___syscall_getdents64 __NR_getdents64 +static inline _syscall3(int, __syscall_getdents64, int, fd, unsigned char *, dirp, size_t, count); + + +ssize_t __getdents64 (int fd, char *buf, size_t nbytes) +{ + struct dirent64 *dp; + off64_t last_offset = -1; + ssize_t retval; + size_t red_nbytes; + struct kernel_dirent64 *skdp, *kdp; + const size_t size_diff = (offsetof (struct dirent64, d_name) + - offsetof (struct kernel_dirent64, d_name)); + + red_nbytes = MIN (nbytes - ((nbytes / + (offsetof (struct dirent64, d_name) + 14)) * size_diff), + nbytes - size_diff); + + dp = (struct dirent64 *) buf; + skdp = kdp = alloca (red_nbytes); + + retval = __syscall_getdents64(fd, (char *)kdp, red_nbytes); + if (retval == -1) + return -1; + + while ((char *) kdp < (char *) skdp + retval) { + const size_t alignment = __alignof__ (struct dirent64); + /* Since kdp->d_reclen is already aligned for the kernel structure + this may compute a value that is bigger than necessary. */ + size_t new_reclen = ((kdp->d_reclen + size_diff + alignment - 1) + & ~(alignment - 1)); + if ((char *) dp + new_reclen > buf + nbytes) { + /* Our heuristic failed. We read too many entries. Reset + the stream. */ + assert (last_offset != -1); + lseek64(fd, last_offset, SEEK_SET); + + if ((char *) dp == buf) { + /* The buffer the user passed in is too small to hold even + one entry. */ + __set_errno (EINVAL); + return -1; + } + break; + } + + last_offset = kdp->d_off; + dp->d_ino = kdp->d_ino; + dp->d_off = kdp->d_off; + dp->d_reclen = new_reclen; + dp->d_type = DT_UNKNOWN; + memcpy (dp->d_name, kdp->d_name, + kdp->d_reclen - offsetof (struct kernel_dirent64, d_name)); + dp = (struct dirent64 *) ((char *) dp + new_reclen); + kdp = (struct kernel_dirent64 *) (((char *) kdp) + kdp->d_reclen); + } + return (char *) dp - buf; +} +#endif /* __UCLIBC_HAS_LFS__ */ + |