aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorRoland McGrath <roland@gnu.org>2002-11-03 03:47:57 +0000
committerRoland McGrath <roland@gnu.org>2002-11-03 03:47:57 +0000
commitc213fe9c7dc7852784fd5303506072a9d01b8d12 (patch)
treed86eca4c8ec5dd519d4d8fb87d64c9cf696d5dd3
parentcd180b205f92263a6dc6d87be56e73c910259af4 (diff)
downloadglibc-c213fe9c7dc7852784fd5303506072a9d01b8d12.tar
glibc-c213fe9c7dc7852784fd5303506072a9d01b8d12.tar.gz
glibc-c213fe9c7dc7852784fd5303506072a9d01b8d12.tar.bz2
glibc-c213fe9c7dc7852784fd5303506072a9d01b8d12.zip
* sysdeps/unix/sysv/linux/getdents.c (__GETDENTS): Use union type for
pointers that can alias. Reported by Daniel Jacobowitz <drow@mvista.com>.
-rw-r--r--ChangeLog4
-rw-r--r--sysdeps/unix/sysv/linux/getdents.c75
2 files changed, 48 insertions, 31 deletions
diff --git a/ChangeLog b/ChangeLog
index 26dcd78a3c..cf31c4f3ab 100644
--- a/ChangeLog
+++ b/ChangeLog
@@ -1,5 +1,9 @@
2002-11-02 Roland McGrath <roland@redhat.com>
+ * sysdeps/unix/sysv/linux/getdents.c (__GETDENTS): Use union type for
+ pointers that can alias.
+ Reported by Daniel Jacobowitz <drow@mvista.com>.
+
* sysdeps/unix/bsd/bsd4.4/freebsd/bits/typesizes.h: New file.
2002-11-02 Roland McGrath <roland@redhat.com>
diff --git a/sysdeps/unix/sysv/linux/getdents.c b/sysdeps/unix/sysv/linux/getdents.c
index 6dc9d714b5..e5796c329c 100644
--- a/sysdeps/unix/sysv/linux/getdents.c
+++ b/sysdeps/unix/sysv/linux/getdents.c
@@ -97,7 +97,6 @@ ssize_t
internal_function
__GETDENTS (int fd, char *buf, size_t nbytes)
{
- DIRENT_TYPE *dp;
off64_t last_offset = -1;
ssize_t retval;
@@ -109,7 +108,12 @@ __GETDENTS (int fd, char *buf, size_t nbytes)
# ifndef __ASSUME_GETDENTS64_SYSCALL
int saved_errno = errno;
# endif
- char *kbuf = buf;
+ union
+ {
+ struct kernel_dirent64 k;
+ DIRENT_TYPE u;
+ char b[1];
+ } *kbuf = (void *) buf, *outp, *inp;
size_t kbytes = nbytes;
if (offsetof (DIRENT_TYPE, d_name)
< offsetof (struct kernel_dirent64, d_name)
@@ -125,7 +129,6 @@ __GETDENTS (int fd, char *buf, size_t nbytes)
if (retval != -1 && errno != -EINVAL)
# endif
{
- struct kernel_dirent64 *kdp;
const size_t size_diff = (offsetof (struct kernel_dirent64, d_name)
- offsetof (DIRENT_TYPE, d_name));
@@ -137,31 +140,43 @@ __GETDENTS (int fd, char *buf, size_t nbytes)
need, don't do any conversions. */
if (offsetof (DIRENT_TYPE, d_name)
== offsetof (struct kernel_dirent64, d_name)
- && sizeof (dp->d_ino) == sizeof (kdp->d_ino)
- && sizeof (dp->d_off) == sizeof (kdp->d_off))
+ && sizeof (outp->u.d_ino) == sizeof (inp->k.d_ino)
+ && sizeof (outp->u.d_off) == sizeof (inp->k.d_off))
return retval;
- dp = (DIRENT_TYPE *)buf;
- kdp = (struct kernel_dirent64 *) kbuf;
- while ((char *) kdp < kbuf + retval)
+ /* These two pointers might alias the same memory buffer.
+ Standard C requires that we always use the same type for them,
+ so we must use the union type. */
+ inp = kbuf;
+ outp = (void *) buf;
+
+ while (&inp->b < &kbuf->b + retval)
{
const size_t alignment = __alignof__ (DIRENT_TYPE);
- /* Since kdp->d_reclen is already aligned for the kernel
+ /* Since inp->k.d_reclen is already aligned for the kernel
structure this may compute a value that is bigger
than necessary. */
- size_t old_reclen = kdp->d_reclen;
+ size_t old_reclen = inp->k.d_reclen;
size_t new_reclen = ((old_reclen - size_diff + alignment - 1)
& ~(alignment - 1));
- uint64_t d_ino = kdp->d_ino;
- int64_t d_off = kdp->d_off;
- unsigned char d_type = kdp->d_type;
-
- DIRENT_SET_DP_INO (dp, d_ino);
- dp->d_off = d_off;
- if ((sizeof (dp->d_ino) != sizeof (kdp->d_ino)
- && dp->d_ino != d_ino)
- || (sizeof (dp->d_off) != sizeof (kdp->d_off)
- && dp->d_off != d_off))
+
+ /* Copy the data out of the old structure into temporary space.
+ Then copy the name, which may overlap if BUF == KBUF. */
+ const uint64_t d_ino = inp->k.d_ino;
+ const int64_t d_off = inp->k.d_off;
+ const uint8_t d_type = inp->k.d_type;
+
+ memmove (outp->u.d_name, inp->k.d_name,
+ old_reclen - offsetof (struct kernel_dirent64, d_name));
+
+ /* Now we have copied the data from INP and access only OUTP. */
+
+ DIRENT_SET_DP_INO (&outp->u, d_ino);
+ outp->u.d_off = d_off;
+ if ((sizeof (outp->u.d_ino) != sizeof (inp->k.d_ino)
+ && outp->u.d_ino != d_ino)
+ || (sizeof (outp->u.d_off) != sizeof (inp->k.d_off)
+ && outp->u.d_off != d_off))
{
/* Overflow. If there was at least one entry
before this one, return them without error,
@@ -169,23 +184,21 @@ __GETDENTS (int fd, char *buf, size_t nbytes)
if (last_offset != -1)
{
__lseek64 (fd, last_offset, SEEK_SET);
- return (char *) dp - buf;
+ return outp->b - buf;
}
__set_errno (EOVERFLOW);
return -1;
}
last_offset = d_off;
- dp->d_reclen = new_reclen;
- dp->d_type = d_type;
- memmove (dp->d_name, kdp->d_name,
- old_reclen - offsetof (struct kernel_dirent64, d_name));
+ outp->u.d_reclen = new_reclen;
+ outp->u.d_type = d_type;
- dp = (DIRENT_TYPE *) ((char *) dp + new_reclen);
- kdp = (struct kernel_dirent64 *) ((char *) kdp + old_reclen);
+ inp = (void *) inp + old_reclen;
+ outp = (void *) outp + new_reclen;
}
- return (char *) dp - buf;
+ return outp->b - buf;
}
# ifndef __ASSUME_GETDENTS64_SYSCALL
@@ -205,7 +218,6 @@ __GETDENTS (int fd, char *buf, size_t nbytes)
* size_diff),
nbytes - size_diff);
- dp = (DIRENT_TYPE *) buf;
skdp = kdp = __alloca (red_nbytes);
retval = INLINE_SYSCALL (getdents, 3, fd,
@@ -214,6 +226,7 @@ __GETDENTS (int fd, char *buf, size_t nbytes)
if (retval == -1)
return -1;
+ DIRENT_TYPE *dp = (DIRENT_TYPE *) buf;
while ((char *) kdp < (char *) skdp + retval)
{
const size_t alignment = __alignof__ (DIRENT_TYPE);
@@ -250,7 +263,7 @@ __GETDENTS (int fd, char *buf, size_t nbytes)
dp = (DIRENT_TYPE *) ((char *) dp + new_reclen);
kdp = (struct kernel_dirent *) (((char *) kdp) + kdp->d_reclen);
}
- }
- return (char *) dp - buf;
+ return (char *) dp - buf;
+ }
}