# The 2.6 asm/stat.h for ARM has some rather unusual transmogrifications
# for big-endian running.  This patch adds ARM specific code in xstatconv.c
# which deals with the 2.4->2.6 ABI change.
--- uClibc-0.9.27/libc/sysdeps/linux/common/xstatconv.c	2005-01-11 23:59:21.000000000 -0800
+++ uClibc-0.9.27/libc/sysdeps/linux/common/xstatconv.c	2005-06-05 11:03:56.742587966 -0700
@@ -18,7 +18,14 @@
    02111-1307 USA. 
    
    Modified for uClibc by Erik Andersen <andersen@codepoet.org>
+   Further modified for ARMBE by John Bowler <jbowler@acm.org>
    */
+/* This is a copy of common/xstatconv.c with a fixup for the ABI
+ * (structure layout) change in ARM Linux 2.6 - this shifts the
+ * st_dev and st_rdev information from the start of the 8 byte
+ * space to the end on big-endian ARM (only).  The code is unchanged
+ * on little endian.
+ */
 
 #define _GNU_SOURCE
 #define _LARGEFILE64_SOURCE
@@ -32,6 +39,84 @@
 #include <sys/stat.h>
 #include "xstatconv.h"
 
+/* Only for ARMEB and LFS. */
+#if defined(__ARMEB__) && defined(__UCLIBC_HAS_LFS__)
+/* stat64 (renamed) from 2.6.11.11.  What happened here is that after
+ * Linux 2.4 the 2.4 unsigned short st_rdev and st_dev fields were
+ * lengthened to unsigned long long - causing the inclusion of at least
+ * some of the 0 padding bytes which followed them.  On little endian
+ * this is fine because 2.4 did zero the pad bytes (I think) and the
+ * position of the data did not change.  On big endian the change
+ * shifted the data to the end of the field.  Someone noticed for the
+ * struct stat, and the armeb (big endian) case preserved the
+ * unsigned short (yuck), but no so for stat64 (maybe this was deliberate,
+ * but there is no evidence in the code of this.)  Consequently a
+ * fixup is necessary for the stat64 case.  The fixup here is to
+ * use the new structure when the change is detected.  See below.
+ */
+struct __kernel_stat64_armeb {
+	/* This definition changes the layout on big-endian from that
+	 * used in 2.4.31 - ABI change!  Likewise for st_rdev.
+	 */
+	unsigned long long	st_dev;
+	unsigned char   __pad0[4];
+	unsigned long	__st_ino;
+	unsigned int	st_mode;
+	unsigned int	st_nlink;
+	unsigned long	st_uid;
+	unsigned long	st_gid;
+	unsigned long long	st_rdev;
+	unsigned char   __pad3[4];
+	long long	st_size;
+	unsigned long	st_blksize;
+	unsigned long   __pad4;
+	unsigned long   st_blocks;
+	unsigned long	st_atime;
+	unsigned long	st_atime_nsec;
+	unsigned long	st_mtime;
+	unsigned long	st_mtime_nsec;
+	unsigned long	st_ctime;
+	unsigned long	st_ctime_nsec;
+	unsigned long long	st_ino;
+};
+
+/* This fixup only works so long as the old struct stat64 is no
+ * smaller than the new one - the caller of xstatconv uses the
+ * *old* struct, but the kernel writes the new one.  CASSERT
+ * detects this at compile time.
+ */
+#define CASSERT(c) do switch (0) { case 0:; case (c):; } while (0)
+
+void __xstat64_conv_new(struct __kernel_stat64_armeb *kbuf, struct stat64 *buf)
+{
+    CASSERT(sizeof *kbuf <= sizeof (struct kernel_stat64));
+
+    /* Convert from new kernel version of `struct stat64'.  */
+    buf->st_dev = kbuf->st_dev;
+    buf->st_ino = kbuf->st_ino;
+#ifdef _HAVE_STAT64___ST_INO
+    buf->__st_ino = kbuf->__st_ino;
+#endif
+    buf->st_mode = kbuf->st_mode;
+    buf->st_nlink = kbuf->st_nlink;
+    buf->st_uid = kbuf->st_uid;
+    buf->st_gid = kbuf->st_gid;
+    buf->st_rdev = kbuf->st_rdev;
+    buf->st_size = kbuf->st_size;
+    buf->st_blksize = kbuf->st_blksize;
+    buf->st_blocks = kbuf->st_blocks;
+    buf->st_atime = kbuf->st_atime;
+    buf->st_mtime = kbuf->st_mtime;
+    buf->st_ctime = kbuf->st_ctime;
+}
+#define _MAY_HAVE_NEW_STAT64 1
+#else
+#define _MAY_HAVE_NEW_STAT64 0
+#endif
+
+/* The following is taken verbatim from xstatconv.c apart from
+ * the addition of the _MAY_HAVE_NEW_STAT64 code.
+ */
 void __xstat_conv(struct kernel_stat *kbuf, struct stat *buf)
 {
     /* Convert to current kernel version of `struct stat'.  */
@@ -53,6 +138,19 @@
 #if defined __UCLIBC_HAS_LFS__
 void __xstat64_conv(struct kernel_stat64 *kbuf, struct stat64 *buf)
 {
+#   if _MAY_HAVE_NEW_STAT64
+    	/* This relies on any device (0,0) not being mountable - i.e.
+	 * it fails on Linux 2.4 if dev(0,0) is a mountable block file
+	 * system and itself contains it's own device.  That doesn't
+	 * happen on Linux 2.4 so far as I can see, but even if it
+	 * does the API only fails (even then) if 2.4 didn't set all
+	 * of the pad bytes to 0 (and it does set them to zero.)
+	 */
+	if (kbuf->st_dev == 0 && kbuf->st_rdev == 0) {
+	    __xstat64_conv_new((struct __kernel_stat64_armeb*)kbuf, buf);
+	    return;
+	}
+#   endif
     /* Convert to current kernel version of `struct stat64'.  */
     buf->st_dev = kbuf->st_dev;
     buf->st_ino = kbuf->st_ino;
