/* fsusage.c -- return space usage of mounted file systems

   Copyright (C) 1991, 1992, 1996, 1998, 1999, 2002, 2003, 2004, 2005, 2006
   Free Software Foundation, Inc.

   This program is free software; you can redistribute it and/or modify
   it under the terms of the GNU General Public License as published by
   the Free Software Foundation; either version 2, or (at your option)
   any later version.

   This program 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 General Public License for more details.

   You should have received a copy of the GNU General Public License
   along with this program; if not, write to the Free Software Foundation,
   Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.  */

#ifdef HAVE_CONFIG_H
#include <config.h>
#endif

#include <stdio.h>
#include <stdlib.h>

#include <limits.h>
#include <sys/types.h>

/* This header needs to be included before sys/mount.h on *BSD */
#ifdef HAVE_SYS_PARAM_H
#  include <sys/param.h>
#endif

#if STAT_STATVFS		/* POSIX 1003.1-2001 (and later) with XSI */
# include <sys/statvfs.h>
#else
/* Don't include backward-compatibility files unless they're needed.
   Eventually we'd like to remove all this cruft.  */
# include <fcntl.h>
# include <unistd.h>
# include <sys/stat.h>
# if HAVE_SYS_PARAM_H
#  include <sys/param.h>
# endif
# if HAVE_SYS_MOUNT_H
#  include <sys/mount.h>
# endif

#if defined (MOUNTED_GETFSSTAT)	/* __alpha running OSF_1 */
#  include <sys/mount.h>
#  include <sys/fs_types.h>
#endif				/* MOUNTED_GETFSSTAT */

#ifdef MOUNTED_GETMNTENT1	/* 4.3BSD, SunOS, HP-UX, Dynix, Irix.  */
#  include <mntent.h>
#  if !defined(MOUNTED)
#    if defined(MNT_MNTTAB)		/* HP-UX.  */
#      define MOUNTED MNT_MNTTAB
#    endif
#    if defined(MNTTABNAME)		/* Dynix.  */
#      define MOUNTED MNTTABNAME
#    endif
#  endif
#endif

#ifdef MOUNTED_GETMNTINFO	/* 4.4BSD.  */
#  include <sys/mount.h>
#endif

#ifdef MOUNTED_GETMNTINFO2	/* NetBSD 3.0.  */
#  include <sys/statvfs.h>
#endif

#ifdef MOUNTED_GETMNT		/* Ultrix.  */
#  include <sys/mount.h>
#  include <sys/fs_types.h>
#endif

#ifdef MOUNTED_FREAD		/* SVR2.  */
#  include <mnttab.h>
#endif

#ifdef MOUNTED_FREAD_FSTYP	/* SVR3.  */
#  include <mnttab.h>
#  include <sys/fstyp.h>
#  include <sys/statfs.h>
#endif

#ifdef MOUNTED_GETMNTENT2	/* SVR4.  */
#  include <sys/mnttab.h>
#endif

#ifdef MOUNTED_VMOUNT		/* AIX.  */
#  include <fshelp.h>
#  include <sys/vfs.h>
#endif

#ifdef HAVE_INFOMOUNT_QNX
#  include <sys/disk.h>
#  include <sys/fsys.h>
#endif

# if HAVE_SYS_VFS_H
#  include <sys/vfs.h>
# endif
# if HAVE_SYS_FS_S5PARAM_H	/* Fujitsu UXP/V */
#  include <sys/fs/s5param.h>
# endif
# if defined HAVE_SYS_FILSYS_H && !defined _CRAY
#  include <sys/filsys.h>	/* SVR2 */
# endif
# if HAVE_SYS_STATFS_H
#  include <sys/statfs.h>
# endif
# if HAVE_DUSTAT_H		/* AIX PS/2 */
#  include <sys/dustat.h>
# endif
# include "full-read.h"
#endif

#ifdef HAVE_SYS_STATVFS_H	/* SVR4.  */
#  include <sys/statvfs.h>
#endif

#include "global.h"
#include "fsusage.h"
#include "util.h"

/* Many space usage primitives use all 1 bits to denote a value that is
   not applicable or unknown.  Propagate this information by returning
   a uintmax_t value that is all 1 bits if X is all 1 bits, even if X
   is unsigned and narrower than uintmax_t.  */
#define PROPAGATE_ALL_ONES(x) \
  ((sizeof (x) < sizeof (uintmax_t) \
    && (~ (x) == (sizeof (x) < sizeof (int) \
		  ? - (1 << (sizeof (x) * CHAR_BIT)) \
		  : 0))) \
   ? UINTMAX_MAX : (uintmax_t) (x))

/* Extract the top bit of X as an uintmax_t value.  */
#define EXTRACT_TOP_BIT(x) ((x) \
			    & ((uintmax_t) 1 << (sizeof (x) * CHAR_BIT - 1)))

/* If a value is negative, many space usage primitives store it into an
   integer variable by assignment, even if the variable's type is unsigned.
   So, if a space usage variable X's top bit is set, convert X to the
   uintmax_t value V such that (- (uintmax_t) V) is the negative of
   the original value.  If X's top bit is clear, just yield X.
   Use PROPAGATE_TOP_BIT if the original value might be negative;
   otherwise, use PROPAGATE_ALL_ONES.  */
#define PROPAGATE_TOP_BIT(x) ((x) | ~ (EXTRACT_TOP_BIT (x) - 1))

/* Fill in the fields of FSP with information about space usage for
   the file system on which FILE resides.
   DISK is the device on which FILE is mounted, for space-getting
   methods that need to know it.
   Return 0 if successful, -1 if not.  When returning -1, ensure that
   ERRNO is either a system error value, or zero if DISK is NULL
   on a system that requires a non-NULL value.  */

#ifdef DOLPHIN
/* So special that it's not worth putting this in autoconf.  */
#undef MOUNTED_FREAD_FSTYP
#define MOUNTED_GETMNTTBL
#endif

#if defined (__QNX__) && !defined(__QNXNTO__) && !defined (HAVE_INFOMOUNT_LIST)
#    define HAVE_INFOMOUNT_QNX
#endif

#if defined(HAVE_INFOMOUNT_LIST) || defined(HAVE_INFOMOUNT_QNX)
#    define HAVE_INFOMOUNT
#endif


#ifdef HAVE_INFOMOUNT_LIST

struct mount_entry *mount_list = NULL;

#  ifdef MOUNTED_GETMNTINFO

#    ifndef HAVE_STRUCT_STATFS_F_FSTYPENAME
static char *fstype_to_string (short t)
{
    switch (t) {
#      ifdef MOUNT_PC
    case MOUNT_PC:
	return "pc";
#      endif
#      ifdef MOUNT_MFS
    case MOUNT_MFS:
	return "mfs";
#      endif
#      ifdef MOUNT_LO
    case MOUNT_LO:
	return "lo";
#      endif
#      ifdef MOUNT_TFS
    case MOUNT_TFS:
	return "tfs";
#      endif
#      ifdef MOUNT_TMP
    case MOUNT_TMP:
	return "tmp";
#      endif
#      ifdef MOUNT_UFS
   case MOUNT_UFS:
     return "ufs" ;
#      endif
#      ifdef MOUNT_NFS
   case MOUNT_NFS:
     return "nfs" ;
#      endif
#      ifdef MOUNT_MSDOS
   case MOUNT_MSDOS:
     return "msdos" ;
#      endif
#      ifdef MOUNT_LFS
   case MOUNT_LFS:
     return "lfs" ;
#      endif
#      ifdef MOUNT_LOFS
   case MOUNT_LOFS:
     return "lofs" ;
#      endif
#      ifdef MOUNT_FDESC
   case MOUNT_FDESC:
     return "fdesc" ;
#      endif
#      ifdef MOUNT_PORTAL
   case MOUNT_PORTAL:
     return "portal" ;
#      endif
#      ifdef MOUNT_NULL
   case MOUNT_NULL:
     return "null" ;
#      endif
#      ifdef MOUNT_UMAP
   case MOUNT_UMAP:
     return "umap" ;
#      endif
#      ifdef MOUNT_KERNFS
   case MOUNT_KERNFS:
     return "kernfs" ;
#      endif
#      ifdef MOUNT_PROCFS
   case MOUNT_PROCFS:
     return "procfs" ;
#      endif
#      ifdef MOUNT_AFS
   case MOUNT_AFS:
     return "afs" ;
#      endif
#      ifdef MOUNT_CD9660
   case MOUNT_CD9660:
     return "cd9660" ;
#      endif
#      ifdef MOUNT_UNION
   case MOUNT_UNION:
     return "union" ;
#      endif
#      ifdef MOUNT_DEVFS
   case MOUNT_DEVFS:
     return "devfs" ;
#      endif
#      ifdef MOUNT_EXT2FS
   case MOUNT_EXT2FS:
     return "ext2fs" ;
#    endif
    default:
	return "?";
    }
}
#  endif /* ! HAVE_STRUCT_STATFS_F_FSTYPENAME */

#endif /* MOUNTED_GETMNTINFO */

#ifdef MOUNTED_VMOUNT		/* AIX.  */
static char *
fstype_to_string (int t)
{
    struct vfs_ent *e;

    e = getvfsbytype (t);
    if (!e || !e->vfsent_name)
	return "none";
    else
	return e->vfsent_name;
}
#endif /* MOUNTED_VMOUNT */

/* Return a list of the currently mounted filesystems, or NULL on error.
   Add each entry to the tail of the list so that they stay in order.
   If NEED_FS_TYPE is nonzero, ensure that the filesystem type fields in
   the returned list are valid.  Otherwise, they might not be.
   If ALL_FS is zero, do not return entries for filesystems that
   are automounter (dummy) entries.  */

struct mount_entry *
read_filesystem_list (int need_fs_type, int all_fs)
{
    struct mount_entry *mlist;
    struct mount_entry *me;
    struct mount_entry *mtail;

    (void) need_fs_type;
    (void) all_fs;

    /* Start the list off with a dummy entry. */
    if (me) {
	if (me->me_devname) free(me->me_devname);
	if (me->me_mountdir) free(me->me_mountdir);
	if (me->me_type) free(me->me_type);
    }
    else
	me = (struct mount_entry *) malloc (sizeof (struct mount_entry));
    me->me_next = NULL;
    mlist = mtail = me;

#ifdef MOUNTED_GETMNTENT1	/* 4.3BSD, SunOS, HP-UX, Dynix, Irix.  */
#  ifdef MOUNTED
    {
	struct mntent *mnt;
	FILE *fp;
	const char *devopt;

	fp = setmntent (MOUNTED, "r");
	if (fp == NULL)
	    return NULL;

	while ((mnt = getmntent (fp))) {
	    if (!all_fs && (!strcmp (mnt->mnt_type, "ignore")
			    || !strcmp (mnt->mnt_type, "auto")))
		continue;

	    me = (struct mount_entry *) malloc (sizeof (struct mount_entry));
	    me->me_devname = strdup (mnt->mnt_fsname);
	    me->me_mountdir = strdup (mnt->mnt_dir);
	    me->me_type = strdup (mnt->mnt_type);
	    devopt = strstr (mnt->mnt_opts, "dev=");
	    if (devopt) {
		if (devopt[4] == '0' && (devopt[5] == 'x' || devopt[5] == 'X'))
		    me->me_dev = xatoi (devopt + 6);
		else
		    me->me_dev = xatoi (devopt + 4);
	    } else
		me->me_dev = -1;	/* Magic; means not known yet. */
	    me->me_next = NULL;

	    /* Add to the linked list. */
	    mtail->me_next = me;
	    mtail = me;
	}

	if (endmntent (fp) == 0)
	    return NULL;
    }
#  endif /* MOUNTED */
#endif /* MOUNTED_GETMNTENT1 */

#ifdef MOUNTED_GETMNTINFO	/* 4.4BSD.  */
    {
	struct statfs *fsp;
	int entries;

	entries = getmntinfo (&fsp, MNT_NOWAIT);
	if (entries < 0)
	    return NULL;
	while (entries-- > 0) {
	    me = (struct mount_entry *) malloc (sizeof (struct mount_entry));
	    me->me_devname = strdup (fsp->f_mntfromname);
	    me->me_mountdir = strdup (fsp->f_mntonname);
#  ifdef HAVE_STRUCT_STATFS_F_FSTYPENAME
	    me->me_type = strdup (fsp->f_fstypename);
#  else
	    me->me_type = fstype_to_string (fsp->f_type);
#  endif
	    me->me_dev = -1;	/* Magic; means not known yet. */
	    me->me_next = NULL;

	    /* Add to the linked list. */
	    mtail->me_next = me;
	    mtail = me;
	    fsp++;
	}
    }
#endif /* MOUNTED_GETMNTINFO */

#ifdef MOUNTED_GETMNTINFO2	/* NetBSD 3.0.  */
    {
	struct statvfs *fsp;
	int entries;

	entries = getmntinfo (&fsp, MNT_NOWAIT);
	if (entries < 0)
	    return NULL;
	for (; entries-- > 0; fsp++) {
	    me = (struct mount_entry *) malloc (sizeof (struct mount_entry));
	    me->me_devname = strdup (fsp->f_mntfromname);
	    me->me_mountdir = strdup (fsp->f_mntonname);
	    me->me_type = strdup (fsp->f_fstypename);
	    me->me_dev = (dev_t) -1;	/* Magic; means not known yet. */

	    /* Add to the linked list. */
	    mtail->me_next = me;
	    mtail = me;
	}
    }
#endif /* MOUNTED_GETMNTINFO2 */

#ifdef MOUNTED_GETMNT		/* Ultrix.  */
    {
	int offset = 0;
	int val;
	struct fs_data fsd;

	while ((val = getmnt (&offset, &fsd, sizeof (fsd), NOSTAT_MANY,
			      NULL)) > 0) {
	    me = (struct mount_entry *) malloc (sizeof (struct mount_entry));
	    me->me_devname = strdup (fsd.fd_req.devname);
	    me->me_mountdir = strdup (fsd.fd_req.path);
	    me->me_type = gt_names[fsd.fd_req.fstype];
	    me->me_dev = fsd.fd_req.dev;
	    me->me_next = NULL;

	    /* Add to the linked list. */
	    mtail->me_next = me;
	    mtail = me;
	}
	if (val < 0)
	    return NULL;
    }
#endif /* MOUNTED_GETMNT */

#ifdef MOUNTED_GETFSSTAT	/* __alpha running OSF_1 */
    {
	int numsys, counter, bufsize;
	struct statfs *stats;

	numsys = getfsstat ((struct statfs *) 0, 0L, MNT_WAIT);
	if (numsys < 0)
	    return (NULL);

	bufsize = (1 + numsys) * sizeof (struct statfs);
	stats = (struct statfs *) malloc (bufsize);
	numsys = getfsstat (stats, bufsize, MNT_WAIT);

	if (numsys < 0) {
	    free (stats);
	    return (NULL);
	}
	for (counter = 0; counter < numsys; counter++) {
	    me = (struct mount_entry *) malloc (sizeof (struct mount_entry));
	    me->me_devname = strdup (stats[counter].f_mntfromname);
	    me->me_mountdir = strdup (stats[counter].f_mntonname);
	    me->me_type = mnt_names[stats[counter].f_type];
	    me->me_dev = -1;	/* Magic; means not known yet. */
	    me->me_next = NULL;

	    /* Add to the linked list. */
	    mtail->me_next = me;
	    mtail = me;
	}

	free (stats);
    }
#endif /* MOUNTED_GETFSSTAT */

#if defined (MOUNTED_FREAD) || defined (MOUNTED_FREAD_FSTYP)	/* SVR[23].  */
    {
	struct mnttab mnt;
	char *table = "/etc/mnttab";
	FILE *fp;

	fp = fopen (table, "r");
	if (fp == NULL)
	    return NULL;

	while (fread (&mnt, sizeof mnt, 1, fp) > 0) {
	    me = (struct mount_entry *) malloc (sizeof (struct mount_entry));
#  ifdef GETFSTYP			/* SVR3.  */
	    me->me_devname = strdup (mnt.mt_dev);
#  else
	    me->me_devname = malloc (strlen (mnt.mt_dev) + 6);
	    strcpy (me->me_devname, "/dev/");
	    strcpy (me->me_devname + 5, mnt.mt_dev);
#  endif
	    me->me_mountdir = strdup (mnt.mt_filsys);
	    me->me_dev = -1;	/* Magic; means not known yet. */
	    me->me_type = "";
#  ifdef GETFSTYP			/* SVR3.  */
	    if (need_fs_type) {
		struct statfs fsd;
		char typebuf[FSTYPSZ];

		if (statfs (me->me_mountdir, &fsd, sizeof fsd, 0) != -1
		    && sysfs (GETFSTYP, fsd.f_fstyp, typebuf) != -1)
		    me->me_type = strdup (typebuf);
	    }
#  endif
	    me->me_next = NULL;

	    /* Add to the linked list. */
	    mtail->me_next = me;
	    mtail = me;
	}

	if (fclose (fp) == EOF)
	    return NULL;
    }
#endif /* MOUNTED_FREAD || MOUNTED_FREAD_FSTYP */

#ifdef MOUNTED_GETMNTTBL	/* DolphinOS goes it's own way */
    {
	struct mntent **mnttbl = getmnttbl (), **ent;
	for (ent = mnttbl; *ent; ent++) {
	    me = (struct mount_entry *) malloc (sizeof (struct mount_entry));
	    me->me_devname = strdup ((*ent)->mt_resource);
	    me->me_mountdir = strdup ((*ent)->mt_directory);
	    me->me_type = strdup ((*ent)->mt_fstype);
	    me->me_dev = -1;	/* Magic; means not known yet. */
	    me->me_next = NULL;

	    /* Add to the linked list. */
	    mtail->me_next = me;
	    mtail = me;
	}
	endmnttbl ();
    }
#endif /* MOUNTED_GETMNTTBL */

#ifdef MOUNTED_GETMNTENT2	/* SVR4.  */
    {
	struct mnttab mnt;
	char *table = MNTTAB;
	FILE *fp;
	int ret;

	fp = fopen (table, "r");
	if (fp == NULL)
	    return NULL;

	while ((ret = getmntent (fp, &mnt)) == 0) {
	    me = (struct mount_entry *) malloc (sizeof (struct mount_entry));
	    me->me_devname = strdup (mnt.mnt_special);
	    me->me_mountdir = strdup (mnt.mnt_mountp);
	    me->me_type = strdup (mnt.mnt_fstype);
	    me->me_dev = -1;	/* Magic; means not known yet. */
	    me->me_next = NULL;
	    /* Add to the linked list. */
	    mtail->me_next = me;
	    mtail = me;
	}

	if (ret > 0)
	    return NULL;
	if (fclose (fp) == EOF)
	    return NULL;
    }
#endif /* MOUNTED_GETMNTENT2 */

#ifdef MOUNTED_VMOUNT		/* AIX.  */
    {
	int bufsize;
	char *entries, *thisent;
	struct vmount *vmp;

	/* Ask how many bytes to allocate for the mounted filesystem info.  */
	mntctl (MCTL_QUERY, sizeof bufsize, (struct vmount *) &bufsize);
	entries = malloc (bufsize);

	/* Get the list of mounted filesystems.  */
	mntctl (MCTL_QUERY, bufsize, (struct vmount *) entries);

	for (thisent = entries; thisent < entries + bufsize;
	     thisent += vmp->vmt_length) {
	    vmp = (struct vmount *) thisent;
	    me = (struct mount_entry *) malloc (sizeof (struct mount_entry));
	    if (vmp->vmt_flags & MNT_REMOTE) {
		char *host, *path;

		/* Prepend the remote pathname.  */
		host = thisent + vmp->vmt_data[VMT_HOSTNAME].vmt_off;
		path = thisent + vmp->vmt_data[VMT_OBJECT].vmt_off;
		me->me_devname = malloc (strlen (host) + strlen (path) + 2);
		strcpy (me->me_devname, host);
		strcat (me->me_devname, ":");
		strcat (me->me_devname, path);
	    } else {
		me->me_devname = strdup (thisent +
				      vmp->vmt_data[VMT_OBJECT].vmt_off);
	    }
	    me->me_mountdir = strdup (thisent + vmp->vmt_data[VMT_STUB].vmt_off);
	    me->me_type = strdup (fstype_to_string (vmp->vmt_gfstype));
	    me->me_dev = -1;	/* vmt_fsid might be the info we want.  */
	    me->me_next = NULL;

	    /* Add to the linked list. */
	    mtail->me_next = me;
	    mtail = me;
	}
	free (entries);
    }
#endif /* MOUNTED_VMOUNT */

    /* Free the dummy head. */
    me = mlist;
    mlist = mlist->me_next;
    free (me);
    return mlist;
}
#endif /* HAVE_INFOMOUNT_LIST */

int get_fs_usage (char const *file, char const *disk, struct fs_usage *fsp)
{
#if defined STAT_STATVFS		/* POSIX */

  struct statvfs fsd;

  if (statvfs (file, &fsd) < 0)
	 return -1;

  /* f_frsize isn't guaranteed to be supported.  */
  fsp->fsu_blocksize = (fsd.f_frsize
			? PROPAGATE_ALL_ONES (fsd.f_frsize)
			: PROPAGATE_ALL_ONES (fsd.f_bsize));

#elif defined STAT_STATFS2_FS_DATA	/* Ultrix */

    struct fs_data fsd;

    if (statfs (file, &fsd) != 1)
	return -1;

    fsp->fsu_blocksize = 1024;
    fsp->fsu_blocks = PROPAGATE_ALL_ONES (fsd.fd_req.btot);
    fsp->fsu_bfree = PROPAGATE_ALL_ONES (fsd.fd_req.bfree);
    fsp->fsu_bavail = PROPAGATE_TOP_BIT (fsd.fd_req.bfreen);
    fsp->fsu_bavail_top_bit_set = EXTRACT_TOP_BIT (fsd.fd_req.bfreen) != 0;
    fsp->fsu_files = PROPAGATE_ALL_ONES (fsd.fd_req.gtot);
    fsp->fsu_ffree = PROPAGATE_ALL_ONES (fsd.fd_req.gfree);

#elif defined STAT_READ_FILSYS		/* SVR2 */
# ifndef SUPERBOFF
#  define SUPERBOFF (SUPERB * 512)
# endif

  struct filsys fsd;
  int fd;

  if (! disk)
    {
      errno = 0;
      return -1;
    }

  fd = open (disk, O_RDONLY);
  if (fd < 0)
    return -1;
  lseek (fd, (off_t) SUPERBOFF, 0);
  if (full_read (fd, (char *) &fsd, sizeof fsd) != sizeof fsd)
    {
      close (fd);
      return -1;
    }
  close (fd);

  fsp->fsu_blocksize = (fsd.s_type == Fs2b ? 1024 : 512);
  fsp->fsu_blocks = PROPAGATE_ALL_ONES (fsd.s_fsize);
  fsp->fsu_bfree = PROPAGATE_ALL_ONES (fsd.s_tfree);
  fsp->fsu_bavail = PROPAGATE_TOP_BIT (fsd.s_tfree);
  fsp->fsu_bavail_top_bit_set = EXTRACT_TOP_BIT (fsd.s_tfree) != 0;
  fsp->fsu_files = (fsd.s_isize == -1
		    ? UINTMAX_MAX
		    : (fsd.s_isize - 2) * INOPB * (fsd.s_type == Fs2b ? 2 : 1));
  fsp->fsu_ffree = PROPAGATE_ALL_ONES (fsd.s_tinode);

#elif defined STAT_STATFS3_OSF1

  struct statfs fsd;

  if (statfs (file, &fsd, sizeof (struct statfs)) != 0)
    return -1;

  fsp->fsu_blocksize = PROPAGATE_ALL_ONES (fsd.f_fsize);

#elif defined STAT_STATFS2_BSIZE	/* 4.3BSD, SunOS 4, HP-UX, AIX */

    struct statfs fsd;

    if (statfs (file, &fsd) < 0)
	return -1;

    fsp->fsu_blocksize = PROPAGATE_ALL_ONES (fsd.f_bsize);

# ifdef STATFS_TRUNCATES_BLOCK_COUNTS

    /* In SunOS 4.1.2, 4.1.3, and 4.1.3_U1, the block counts in the
       struct statfs are truncated to 2GB.  These conditions detect that
       truncation, presumably without botching the 4.1.1 case, in which
       the values are not truncated.  The correct counts are stored in
       undocumented spare fields.  */
    if (fsd.f_blocks == 0x7fffffff / fsd.f_bsize && fsd.f_spare[0] > 0)
      {
        fsd.f_blocks = fsd.f_spare[0];
        fsd.f_bfree = fsd.f_spare[1];
        fsd.f_bavail = fsd.f_spare[2];
      }
# endif /* STATFS_TRUNCATES_BLOCK_COUNTS */

#elif defined STAT_STATFS2_FSIZE	/* 4.4BSD */

    struct statfs fsd;

    if (statfs (file, &fsd) < 0)
	return -1;

    fsp->fsu_blocksize = PROPAGATE_ALL_ONES (fsd.f_fsize);

#elif defined STAT_STATFS4		/* SVR3, Dynix, Irix, AIX */

# if !_AIX && !defined _SEQUENT_ && !defined DOLPHIN
#  define f_bavail f_bfree
# endif

    struct statfs fsd;

    if (statfs (file, &fsd, sizeof fsd, 0) < 0)
	return -1;

    /* Empirically, the block counts on most SVR3 and SVR3-derived
       systems seem to always be in terms of 512-byte blocks,
       no matter what value f_bsize has.  */
# if _AIX || defined _CRAY
   fsp->fsu_blocksize = PROPAGATE_ALL_ONES (fsd.f_bsize);
# else
   fsp->fsu_blocksize = 512;
# endif

#endif

#if (defined STAT_STATVFS \
     || (!defined STAT_STATFS2_FS_DATA && !defined STAT_READ_FILSYS))

    fsp->fsu_blocks = PROPAGATE_ALL_ONES (fsd.f_blocks);
    fsp->fsu_bfree = PROPAGATE_ALL_ONES (fsd.f_bfree);
    fsp->fsu_bavail = PROPAGATE_TOP_BIT (fsd.f_bavail);
    fsp->fsu_bavail_top_bit_set = EXTRACT_TOP_BIT (fsd.f_bavail) != 0;
    fsp->fsu_files = PROPAGATE_ALL_ONES (fsd.f_files);
    fsp->fsu_ffree = PROPAGATE_ALL_ONES (fsd.f_ffree);

#endif

    return 0;
}

#if defined _AIX && defined _I386
/* AIX PS/2 does not supply statfs.  */
static int statfs (char *file, struct statfs *fsb)
{
    struct stat stats;
    struct dustat fsd;

    if (stat (file, &stats) != 0)
	return -1;
    if (dustat (stats.st_dev, 0, &fsd, sizeof (fsd)))
	return -1;
    fsb->f_type = 0;
    fsb->f_bsize = fsd.du_bsize;
    fsb->f_blocks = fsd.du_fsize - fsd.du_isize;
    fsb->f_bfree = fsd.du_tfree;
    fsb->f_bavail = fsd.du_tfree;
    fsb->f_files = (fsd.du_isize - 2) * fsd.du_inopb;
    fsb->f_ffree = fsd.du_tinode;
    fsb->f_fsid.val[0] = fsd.du_site;
    fsb->f_fsid.val[1] = fsd.du_pckno;
    return 0;
}
#endif /* _AIX && _I386 */
