I'm running on CentOS 5.4 (but this bug appears to go back a long ways). I have a file system mounted which has a 64-bit FSID, of which bit 31 is set: 000486ba.a1297d6b Note that the lower 32 bits of the FSID, if interpreted as a signed number, are negative. If I apply fstatvfs() to this file system, I get an incorrect FSID, because the lower half is sign-extended into the upper half: fstatvfs returns: ffffffff.a1297d6b If I call fstatfs() instead, I get: 000486ba.a1297d6b I traced the problem down to the INTERNAL_STATVFS routine in glibc/sysdeps/unix/sysv/linux/internal_statvfs.c. (This is not quite the newest version, as the source tree I had handy was dated 2009-09-09, but I don't see any recent changes in this area.) The fsid is assigned here: if (sizeof (buf->f_fsid) == sizeof (fsbuf->f_fsid)) buf->f_fsid = (fsbuf->f_fsid.__val[0] | ((unsigned long int) fsbuf->f_fsid.__val[1] << (8 * (sizeof (buf->f_fsid) - sizeof (fsbuf->f_fsid.__val[0]))))); Note that ' fsbuf->f_fsid.__val[0] ' is not cast to an unsigned value. f_fsid is defined in glibc/bits/typesizes.h as a structure of two 'int' values. Thus f_fsid, which is a 64-bit value, gets assigned a 32-bit signed 'int' value which is ORed against the upper 32 bits. If the lower half has its sign bit set, the f_fsid will be incorrect. Luckily, there is an easy workaround, as the statfs() and fstatfs() calls don't suffer from this problem. (But they're not POSIX, either.)
A really simple test program, if you happen to have a file system around that delivers these FSIDs.... #include <stdio.h> #include <sys/statfs.h> #include <sys/statvfs.h> #include <fcntl.h> void main(int argc, char *argv[]) { int fd; struct statvfs v; struct statfs suck; if (argc != 2) { printf("need mountpoint\n"); return; } fd = open(argv[1], O_RDONLY); if (fd < 0) perror("open"); if (fstatvfs(fd, &v) < 0) perror("fstatvfs"); printf("%016lx\n", v.f_fsid); if (fstatfs(fd, &suck) < 0) perror("fstatfs"); printf("%016lx\n", suck.f_fsid); }
For better correctness, the test program should probably do something like unsigned long id; memcpy(&id, &suck.f_fsid, sizeof(id)); printf("%016lx\n", id); rather than just passing the structure to printf and expecting the right thing to happen, but either shows the correct FSID on x86_64.
Fixed in git.