/*
 * CDDL HEADER START
 *
 * The contents of this file are subject to the terms of the
 * Common Development and Distribution License (the "License").
 * You may not use this file except in compliance with the License.
 *
 * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
 * or http://www.opensolaris.org/os/licensing.
 * See the License for the specific language governing permissions
 * and limitations under the License.
 *
 * When distributing Covered Code, include this CDDL HEADER in each
 * file and include the License file at usr/src/OPENSOLARIS.LICENSE.
 * If applicable, add the following below this CDDL HEADER, with the
 * fields enclosed by brackets "[]" replaced with your own identifying
 * information: Portions Copyright [yyyy] [name of copyright owner]
 *
 * CDDL HEADER END
 */
/*
 * Copyright 2009 Sun Microsystems, Inc.  All rights reserved.
 * Use is subject to license terms.
 */

/*	Copyright (c) 1984, 1986, 1987, 1988, 1989 AT&T	*/
/*	  All Rights Reserved  	*/

/*
 * University Copyright- Copyright (c) 1982, 1986, 1988
 * The Regents of the University of California
 * All Rights Reserved
 *
 * University Acknowledgment- Portions of this document are derived from
 * software developed by the University of California, Berkeley, and its
 * contributors.
 */


/*
 * df
 */
#include <stdio.h>
#include <fcntl.h>
#include <sys/param.h>
#include <sys/types.h>
#include <sys/mntent.h>
#include <sys/fs/ufs_fs.h>
#include <sys/stat.h>
#include <sys/vfs.h>
#include <sys/file.h>
#include <sys/statvfs.h>
#include <sys/mnttab.h>
#include <sys/mkdev.h>
#include <locale.h>
#include <stdarg.h>
#include <string.h>
#include <errno.h>
#include <libintl.h>

extern char	*getenv();
extern char	*getcwd();
extern char	*realpath();
extern off_t	lseek();

/*
 * Raw name to block device name translation function.
 * This comes from libadm.
 */
extern char 	*getfullblkname();

static  void		usage(), pheader();
static  char		*mpath(char *);
static  char		*zap_chroot(char *);
static  char		*pathsuffix(char *, char *);
static  char		*xmalloc(unsigned int);
static  int		chroot_stat(char *, int (*)(), char *, char **);
static  int		bread(char *, int, daddr_t, char *, int);
static  int		subpath(char *, char *);
static	int		abspath(char *, char *, char *);
static  void		show_inode_usage();
static  void		dfreedev(char *);
static  void		dfreemnt(char *, struct mnttab *);
static  void		print_totals();
static  void		print_itotals();
static  void		print_statvfs(struct statvfs64 *);
static  int 		mdev(char *, struct mnttab **);
static struct mntlist	*mkmntlist();
static struct mnttab	*mntdup(struct mnttab *mnt);
static struct mntlist	*findmntent(char *, struct stat64 *, struct mntlist *);

#define	bcopy(f, t, n)	memcpy(t, f, n)
#define	bzero(s, n)	memset(s, 0, n)
#define	bcmp(s, d, n)	memcmp(s, d, n)

#define	index(s, r)	strchr(s, r)
#define	rindex(s, r)	strrchr(s, r)

#define	dbtok(x, b) \
	((b) < (fsblkcnt64_t)1024 ? \
	(x) / ((fsblkcnt64_t)1024 / (b)) : (x) * ((b) / (fsblkcnt64_t)1024))

int	aflag = 0;		/* even the uninteresting ones */
int	bflag = 0;		/* print only number of kilobytes free */
int	eflag = 0;		/* print only number of file entries free */
int	gflag = 0;		/* print entire statvfs structure */
int	hflag = 0;		/* don't print header */
int	iflag = 0;		/* information for inodes */
int	nflag = 0;		/* print VFStype name */
int	tflag = 0;		/* print totals */
int	errflag = 0;
int 	errcode = 0;
char	*typestr = "ufs";
fsblkcnt64_t	t_totalblks, t_avail, t_free, t_used, t_reserved;
int	t_inodes, t_iused, t_ifree;

/*
 * cached information recording previous chroot history.
 */
static	char	*chrootpath;

extern	int	optind;
extern	char	*optarg;

union {
	struct fs iu_fs;
	char dummy[SBSIZE];
} sb;
#define	sblock	sb.iu_fs

/*
 * This structure is used to chain mntent structures into a list
 * and to cache stat information for each member of the list.
 */
struct mntlist {
	struct mnttab	*mntl_mnt;
	struct mntlist	*mntl_next;
	dev_t		mntl_dev;
	int		mntl_devvalid;
};

char *subopts [] = {
#define	A_FLAG		0
	"a",
#define	I_FLAG		1
	"i",
	NULL
};

int
main(int argc, char *argv[])
{
	struct mnttab 		mnt;
	int			opt;
	char			*suboptions, *value;

	(void) setlocale(LC_ALL, "");
#if !defined(TEXT_DOMAIN)	/* Should be defined by cc -D */
#define	TEXT_DOMAIN	"SYS_TEST"	/* Use this only if it weren't */
#endif
	(void) textdomain(TEXT_DOMAIN);

	while ((opt = getopt(argc, argv, "beghkno:t")) != EOF) {
		switch (opt) {

		case 'b':	/* print only number of kilobytes free */
			bflag++;
			break;

		case 'e':
			eflag++; /* print only number of file entries free */
			iflag++;
			break;

		case 'g':
			gflag++;
			break;

		case 'n':
			nflag++;
			break;

		case 'k':
			break;

		case 'h':
			hflag++;
			break;

		case 'o':
			/*
			 * ufs specific options.
			 */
			suboptions = optarg;
			while (*suboptions != '\0') {
				switch (getsubopt(&suboptions,
				    subopts, &value)) {

				case I_FLAG:	/* information for inodes */
					iflag++;
					break;

				default:
					usage();
				}
			}
			break;

		case 't':		/* print totals */
			tflag++;
			break;

		case 'V':		/* Print command line */
			{
				char			*opt_text;
				int			opt_count;

				(void) fprintf(stdout, "df -F ufs ");
				for (opt_count = 1; opt_count < argc;
				    opt_count++) {
					opt_text = argv[opt_count];
					if (opt_text)
						(void) fprintf(stdout, " %s ",
						    opt_text);
				}
				(void) fprintf(stdout, "\n");
			}
			break;

		case '?':
			errflag++;
		}
	}
	if (errflag)
		usage();
	if (gflag && iflag) {
		printf(gettext("df: '-g' and '-o i' are mutually exclusive\n"));
		exit(1);
	}
	if (bflag || eflag)
		tflag = 0;

	/*
	 * Cache CHROOT information for later use; assume that $CHROOT matches
	 * the cumulative arguments given to chroot calls.
	 */
	chrootpath = getenv("CHROOT");
	if (chrootpath != NULL && strcmp(chrootpath, "/") == 0)
		chrootpath = NULL;

	if (argc <= optind) {
		/*
		 * Take this path when "/usr/lib/fs/ufs/df" is specified, and
		 * there are no mountpoints specified.
		 * E.g., these command lines take us down this path
		 * 	/usr/lib/fs/ufs/df -o i
		 *	/usr/lib/fs/ufs/df
		 */
		FILE *mtabp;

		if ((mtabp = fopen(MNTTAB, "r")) == NULL) {
			(void) fprintf(stderr, "df: ");
			perror(MNTTAB);
			exit(1);
		}
		pheader();
		while (getmntent(mtabp, &mnt) == 0) {
			if (strcmp(typestr, mnt.mnt_fstype) != 0) {
				continue;
			}
			dfreemnt(mnt.mnt_mountp, &mnt);
		}
		if (tflag)
			if (iflag)
				print_itotals();
			else
				print_totals();
		(void) fclose(mtabp);
	} else {
		int i;
		struct mntlist *mntl;
		struct stat64    *argstat;
		char **devnames;
		char *cp;

		/* Arguments are processed till optind, adjust the pointers */
		argv += optind;
		argc -= optind;

		/*
		 * Obtain stat64 information for each argument before
		 * constructing the list of mounted file systems.  This
		 * ordering forces the automounter to establish any
		 * mounts required to access the arguments, so that the
		 * corresponding mount table entries will exist when
		 * we look for them.
		 */
		argstat = (struct stat64 *)xmalloc(argc * sizeof (*argstat));
		devnames = (char **)xmalloc(argc * sizeof (char *));
		for (i = 0; i < argc; i++) {

			/*
			 * Given a raw device name, get the block device name
			 */
			cp = getfullblkname(argv[i]);
			if (cp == NULL || *cp == '\0') {
				if (cp != NULL)
					free(cp);
				cp = strdup(argv[i]);

				if (cp == NULL) {
					int j;

					fprintf(stderr, gettext(
					"df: memory allocation failure\n"));

					for (j = 0; j < i; j++)
						free(devnames[j]);
					free(devnames);
					free(argstat);
					exit(1);
				}
			}
			if (stat64(cp, &argstat[i]) < 0) {
				errcode = errno;
				/*
				 * Mark as no longer interesting.
				 */
				argv[i] = NULL;
				devnames[i] = NULL;
				free(cp);
			} else {
				devnames[i] = cp;
			}
		}

		pheader();
		aflag++;
		/*
		 * Construct the list of mounted file systems.
		 */
		mntl = mkmntlist();

		/*
		 * Iterate through the argument list, reporting on each one.
		 */
		for (i = 0; i < argc; i++) {
			struct mntlist *mlp;
			int isblk;

			/*
			 * Skip if we've already determined that we can't
			 * process it.
			 */
			if (argv[i] == NULL)
				continue;

			/*
			 * If the argument names a device, report on the file
			 * system associated with the device rather than on
			 * the one containing the device's directory entry
			 */
			cp = devnames[i];
			if ((isblk = (argstat[i].st_mode&S_IFMT) == S_IFBLK) ||
			    (argstat[i].st_mode & S_IFMT) == S_IFCHR) {
				if (isblk && strcmp(mpath(cp), "") != 0) {
					struct mnttab *mp;
					if (mdev(cp, &mp))
						return (1);
					dfreemnt(mp->mnt_mountp, mp);
				} else {
					dfreedev(cp);
				}
				free(cp);
				devnames[i] = NULL;
				continue;
			}

			/*
			 * Get this argument's corresponding mount table
			 * entry.
			 */
			mlp = findmntent(cp, &argstat[i], mntl);
			free(cp);
			devnames[i] = NULL;

			if (mlp == NULL) {
				(void) fprintf(stderr,
				gettext("Could not find mount point for %s\n"),
				    argv[i]);
				continue;
			}

			dfreemnt(mlp->mntl_mnt->mnt_mountp, mlp->mntl_mnt);
		}
		free(devnames);
		free(argstat);
	}
	return (0);
}

void
pheader()
{
	if (hflag)
		return;
	if (nflag)
		(void) printf(gettext("VFStype name - ufs\n"));
	if (iflag) {
		if (eflag)
			/*
			 * TRANSLATION_NOTE
			 * Following string is used as a table header.
			 * Translated items should start at the same
			 * columns as the original items.
			 */
			(void) printf(gettext(
"Filesystem             ifree\n"));
		else {
			/*
			 * TRANSLATION_NOTE
			 * Following string is used as a table header.
			 * Translated items should start at the same
			 * columns as the original items.
			 */
			(void) printf(gettext(
"Filesystem             iused   ifree  %%iused  Mounted on\n"));
		}
	} else {
		if (gflag)
			/*
			 * TRANSLATION_NOTE
			 * Following string is used as a table header.
			 * Translated items should start at the same
			 * columns as the original items.
			 */
			(void) printf(gettext(
"Filesystem        f_type f_fsize f_bfree f_bavail f_files f_ffree "
"f_fsid f_flag f_fstr\n"));
		else
			if (bflag)
				/*
				 * TRANSLATION_NOTE
				 * Following string is used as a table header.
				 * Translated items should start at the same
				 * columns as the original items.
				 */
				(void) printf(gettext(
"Filesystem             avail\n"));
			else {
				/*
				 * TRANSLATION_NOTE
				 * Following string is used as a table header.
				 * Translated items should start at the same
				 * columns as the original items.
				 */
				(void) printf(gettext(
"Filesystem            kbytes    used   avail capacity  Mounted on\n"));
			}
		}
}

/*
 * Report on a block or character special device. Assumed not to be
 * mounted.  N.B. checks for a valid UFS superblock.
 */
void
dfreedev(char *file)
{
	fsblkcnt64_t totalblks, availblks, avail, free, used;
	int fi;

	fi = open64(file, 0);
	if (fi < 0) {
		(void) fprintf(stderr, "df: ");
		perror(file);
		return;
	}
	if (bread(file, fi, SBLOCK, (char *)&sblock, SBSIZE) == 0) {
		(void) close(fi);
		return;
	}
	if ((sblock.fs_magic != FS_MAGIC) &&
	    (sblock.fs_magic != MTB_UFS_MAGIC)) {
		(void) fprintf(stderr, gettext(
"df: %s: not a ufs file system\n"),
		    file);
		(void) close(fi);
		return;
	}
	if (sblock.fs_magic == FS_MAGIC &&
	    (sblock.fs_version != UFS_EFISTYLE4NONEFI_VERSION_2 &&
	    sblock.fs_version != UFS_VERSION_MIN)) {
		(void) fprintf(stderr, gettext(
"df: %s: unrecognized version of UFS: %d\n"),
		    file, sblock.fs_version);
		(void) close(fi);
		return;
	}
	if (sblock.fs_magic == MTB_UFS_MAGIC &&
	    (sblock.fs_version > MTB_UFS_VERSION_1 ||
	    sblock.fs_version < MTB_UFS_VERSION_MIN)) {
		(void) fprintf(stderr, gettext(
"df: %s: unrecognized version of UFS: %d\n"),
		    file, sblock.fs_version);
		(void) close(fi);
		return;
	}
	(void) printf("%-20.20s", file);
	if (iflag) {
		if (eflag) {
			(void) printf("%8ld", sblock.fs_cstotal.cs_nifree);
		} else {
			show_inode_usage(
			    (fsfilcnt64_t)sblock.fs_ncg *
			    (fsfilcnt64_t)sblock.fs_ipg,
			    (fsfilcnt64_t)sblock.fs_cstotal.cs_nifree);
		}
	} else {
		totalblks = (fsblkcnt64_t)sblock.fs_dsize;
		free =
		    (fsblkcnt64_t)sblock.fs_cstotal.cs_nbfree *
		    (fsblkcnt64_t)sblock.fs_frag +
		    (fsblkcnt64_t)sblock.fs_cstotal.cs_nffree;
		used = totalblks - free;
		availblks = totalblks / (fsblkcnt64_t)100 *
		    ((fsblkcnt64_t)100 - (fsblkcnt64_t)sblock.fs_minfree);
		avail = availblks > used ? availblks - used : (fsblkcnt64_t)0;
		if (bflag) {
			(void) printf("%8lld\n", dbtok(avail,
			    (fsblkcnt64_t)sblock.fs_fsize));
		} else {
			(void) printf(" %7lld %7lld %7lld",
			    dbtok(totalblks, (fsblkcnt64_t)sblock.fs_fsize),
			    dbtok(used, (fsblkcnt64_t)sblock.fs_fsize),
			    dbtok(avail, (fsblkcnt64_t)sblock.fs_fsize));
			(void) printf("%6.0f%%",
			    availblks == 0 ? 0.0 :
			    (double)used / (double)availblks * 100.0);
			(void) printf("  ");
		}
		if (tflag) {
			t_totalblks += dbtok(totalblks,
			    (fsblkcnt64_t)sblock.fs_fsize);
			t_used += dbtok(used, (fsblkcnt64_t)sblock.fs_fsize);
			t_avail += dbtok(avail, (fsblkcnt64_t)sblock.fs_fsize);
			t_free += free;
		}
	}
	if ((!bflag) && (!eflag))
		(void) printf("  %s\n", mpath(file));
	else if (eflag)
		(void) printf("\n");
	(void) close(fi);
}

void
dfreemnt(char *file, struct mnttab *mnt)
{
	struct statvfs64 fs;

	if (statvfs64(file, &fs) < 0 &&
	    chroot_stat(file, statvfs64, (char *)&fs, &file) < 0) {
		(void) fprintf(stderr, "df: ");
		perror(file);
		return;
	}

	if (!aflag && fs.f_blocks == 0) {
		return;
	}
	if (!isatty(fileno(stdout))) {
		(void) printf("%s", mnt->mnt_special);
	} else {
		if (strlen(mnt->mnt_special) > (size_t)20) {
			(void) printf("%s\n", mnt->mnt_special);
			(void) printf("                    ");
		} else {
			(void) printf("%-20.20s", mnt->mnt_special);
		}
	}
	if (iflag) {
		if (eflag) {
			(void) printf("%8lld", fs.f_ffree);
		} else {
			show_inode_usage(fs.f_files, fs.f_ffree);
		}
	} else {
		if (gflag) {
			print_statvfs(&fs);
		} else {
			fsblkcnt64_t totalblks, avail, free, used, reserved;

			totalblks = fs.f_blocks;
			free = fs.f_bfree;
			used = totalblks - free;
			avail = fs.f_bavail;
			reserved = free - avail;
			if ((long long)avail < 0)
				avail = 0;
			if (bflag) {
				(void) printf("%8lld\n", dbtok(avail,
				    (fsblkcnt64_t)fs.f_frsize));
			} else {
				(void) printf(" %7lld %7lld %7lld",
				    dbtok(totalblks,
				    (fsblkcnt64_t)fs.f_frsize),
				    dbtok(used, (fsblkcnt64_t)fs.f_frsize),
				    dbtok(avail, (fsblkcnt64_t)fs.f_frsize));
				totalblks -= reserved;
				(void) printf("%6.0f%%",
				    totalblks == 0 ? 0.0 :
				    (double)used / (double)totalblks * 100.0);
				(void) printf("  ");
				if (tflag) {
				t_totalblks += dbtok(totalblks + reserved,
				    (fsblkcnt64_t)fs.f_bsize);
				t_reserved += reserved;
				t_used += dbtok(used,
				    (fsblkcnt64_t)fs.f_frsize);
				t_avail += dbtok(avail,
				    (fsblkcnt64_t)fs.f_frsize);
				t_free += free;
				}
			}
		}
	}
	if ((!bflag) && (!eflag) && (!gflag))
		(void) printf("  %s\n", mnt->mnt_mountp);
	else if (eflag)
		(void) printf("\n");
}

static void
show_inode_usage(fsfilcnt64_t total, fsfilcnt64_t free)
{
	fsfilcnt64_t used = total - free;
	int missing_info = ((long long)total == (long long)-1 ||
	    (long long)free == (long long)-1);

	if (missing_info)
		(void) printf("%8s", "*");
	else
		(void) printf("%8lld", used);
	if ((long long)free == (long long)-1)
		(void) printf("%8s", "*");
	else
		(void) printf(" %7lld", free);
	if (missing_info)
		(void) printf("%6s  ", "*");
	else
		(void) printf("%6.0f%% ", (double)used / (double)total * 100.0);
}

/*
 * Return the suffix of path obtained by stripping off the prefix
 * that is the value of the CHROOT environment variable.  If this
 * value isn't obtainable or if it's not a prefix of path, return NULL.
 */
static char *
zap_chroot(char *path)
{
	return (pathsuffix(path, chrootpath));
}

/*
 * Stat/statfs a file after stripping off leading directory to which we are
 * chroot'd.  Used to find the TFS mount that applies to the current
 * activated NSE environment.
 */
static int
chroot_stat(char *dir, int (*statfunc)(), char *statp, char **dirp)
{
	if ((dir = zap_chroot(dir)) == NULL)
		return (-1);
	if (dirp)
		*dirp = dir;
	return (*statfunc)(dir, statp);
}

/*
 * Given a name like /dev/dsk/c1d0s2, returns the mounted path, like /usr.
 */
char *
mpath(char *file)
{
	struct mnttab mnt;
	FILE *mnttab;
	struct stat64 device_stat, mount_stat;
	char *mname;

	mnttab = fopen(MNTTAB, "r");
	if (mnttab == NULL) {
		return ("");
	}
	mname = "";
	while ((getmntent(mnttab, &mnt)) == 0) {
		if (strcmp(mnt.mnt_fstype, MNTTYPE_UFS) != 0) {
			continue;
		}
		if (strcmp(file, mnt.mnt_special) == 0) {
			if (stat64(mnt.mnt_mountp, &mount_stat) != 0)
				continue;
			if (stat64(mnt.mnt_special, &device_stat) != 0)
				continue;

			if (device_stat.st_rdev == mount_stat.st_dev) {
				mname = mnt.mnt_mountp;
				break;
			}
		}
	}
	fclose(mnttab);
	return (mname);
}

/*
 * Given a special device, return mnttab entry
 * Returns 0 on success
 */

int
mdev(char *spec, struct mnttab **mntbp)
{
	FILE *mntp;
	struct mnttab mnt;

	if ((mntp = fopen(MNTTAB, "r")) == 0) {
		(void) fprintf(stderr, "df: ");
		perror(MNTTAB);
		return (1);
	}

	while (getmntent(mntp, &mnt) == 0) {
		if (strcmp(spec, mnt.mnt_special) == 0) {
			(void) fclose(mntp);
			*mntbp =  mntdup(&mnt);
			return (0);
		}
	}
	(void) fclose(mntp);
	(void) fprintf(stderr, "df : couldn't find mnttab entry for %s", spec);
	return (1);
}

/*
 * Find the entry in mlist that corresponds to the file named by path
 * (i.e., that names a mount table entry for the file system in which
 * path lies).  The pstat argument must point to stat information for
 * path.
 *
 * Return the entry or NULL if there's no match.
 *
 * As it becomes necessary to obtain stat information about previously
 * unexamined mlist entries, gather the information and cache it with the
 * entries.
 *
 * The routine's strategy is to convert path into its canonical, symlink-free
 * representation canon (which will require accessing the file systems on the
 * branch from the root to path and thus may cause the routine to hang if any
 * of them are inaccessible) and to use it to search for a mount point whose
 * name is a substring of canon and whose corresponding device matches that of
 * canon.  This technique avoids accessing unnecessary file system resources
 * and thus prevents the program from hanging on inaccessible resources unless
 * those resources are necessary for accessing path.
 */
static struct mntlist *
findmntent(char *path, struct stat64 *pstat, struct mntlist *mlist)
{
	static char		cwd[MAXPATHLEN];
	char			canon[MAXPATHLEN];
	char			scratch[MAXPATHLEN];
	struct mntlist *mlp;

	/*
	 * If path is relative and we haven't already determined the current
	 * working directory, do so now.  Calculating the working directory
	 * here lets us do the work once, instead of (potentially) repeatedly
	 * in realpath().
	 */
	if (*path != '/' && cwd[0] == '\0') {
		if (getcwd(cwd, MAXPATHLEN) == NULL) {
			cwd[0] = '\0';
			return (NULL);
		}
	}

	/*
	 * Find an absolute pathname in the native file system name space that
	 * corresponds to path, stuffing it into canon.
	 *
	 * If CHROOT is set in the environment, assume that chroot($CHROOT)
	 * (or an equivalent series of calls) was executed and convert the
	 * path to the equivalent name in the native file system's name space.
	 * Doing so allows direct comparison with the names in mtab entires,
	 * which are assumed to be recorded relative to the native name space.
	 */
	if (abspath(cwd, path, scratch) < 0)
		return (NULL);
	if (strcmp(scratch, "/") == 0 && chrootpath != NULL) {
		/*
		 * Force canon to be in canonical form; if the result from
		 * abspath was "/" and chrootpath isn't the null string, we
		 * must strip off a trailing slash.
		 */
		scratch[0] = '\0';
	}
	(void) sprintf(canon, "%s%s", chrootpath ? chrootpath : "", scratch);

again:
	for (mlp = mlist; mlp; mlp = mlp->mntl_next) {
		struct mnttab *mnt = mlp->mntl_mnt;

		/*
		 * Ignore uninteresting mounts.
		 */
		if (strcmp(mnt->mnt_fstype, typestr) != 0)
			continue;

		/*
		 * The mount entry covers some prefix of the file.
		 * See whether it's the entry for the file system
		 * containing the file by comparing device ids.
		 */
		if (mlp->mntl_dev == NODEV) {
			struct stat64 fs_sb;

			if (stat64(mnt->mnt_mountp, &fs_sb) < 0 &&
			    chroot_stat(mnt->mnt_mountp, stat64, (char *)&fs_sb,
			    (char **)NULL) < 0) {
				continue;
			}
			mlp->mntl_dev = fs_sb.st_dev;
		}

		if (pstat->st_dev == mlp->mntl_dev)
			return (mlp);
	}

	return (NULL);
}

/*
 * Convert the path given in raw to canonical, absolute, symlink-free
 * form, storing the result in the buffer named by canon, which must be
 * at least MAXPATHLEN bytes long.  "wd" contains the current working
 * directory; accepting this value as an argument lets our caller cache
 * the value, so that realpath (called from this routine) doesn't have
 * to recalculate it each time it's given a relative pathname.
 *
 * Return 0 on success, -1 on failure.
 */
static int
abspath(char *wd, char *raw, char *canon)
{
	char		absbuf[MAXPATHLEN];

	/*
	 * Preliminary sanity check.
	 */
	if (wd == NULL || raw == NULL || canon == NULL)
		return (-1);

	/*
	 * If the path is relative, convert it to absolute form,
	 * using wd if it's been supplied.
	 */
	if (raw[0] != '/') {
		char	*limit = absbuf + sizeof (absbuf);
		char	*d;

		/* Fill in working directory. */
		if (strlcpy(absbuf, wd, sizeof (absbuf)) >= sizeof (absbuf))
			return (-1);

		/* Add separating slash. */
		d = absbuf + strlen(absbuf);
		if (d < limit)
			*d++ = '/';

		/* Glue on the relative part of the path. */
		while (d < limit && (*d++ = *raw++))
			continue;

		raw = absbuf;
	}

	/*
	 * Call realpath to canonicalize and resolve symlinks.
	 */
	return (realpath(raw, canon) == NULL ? -1 : 0);
}

/*
 * Return a pointer to the trailing suffix of full that follows the prefix
 * given by pref.  If pref isn't a prefix of full, return NULL.  Apply
 * pathname semantics to the prefix test, so that pref must match at a
 * component boundary.
 */
static char *
pathsuffix(char *full, char *pref)
{
	int preflen;

	if (full == NULL || pref == NULL)
		return (NULL);

	preflen = strlen(pref);
	if (strncmp(pref, full, preflen) != 0)
		return (NULL);

	/*
	 * pref is a substring of full.  To be a subpath, it cannot cover a
	 * partial component of full.  The last clause of the test handles the
	 * special case of the root.
	 */
	if (full[preflen] != '\0' && full[preflen] != '/' && preflen > 1)
		return (NULL);

	if (preflen == 1 && full[0] == '/')
		return (full);
	else
		return (full + preflen);
}

/*
 * Return zero iff the path named by sub is a leading subpath
 * of the path named by full.
 *
 * Treat null paths as matching nothing.
 */
static int
subpath(char *full, char *sub)
{
	return (pathsuffix(full, sub) == NULL);
}

offset_t llseek();

int
bread(char *file, int fi, daddr_t bno, char *buf, int cnt)
{
	int n;

	(void) llseek(fi, (offset_t)bno * DEV_BSIZE, 0);
	if ((n = read(fi, buf, cnt)) < 0) {
		/* probably a dismounted disk if errno == EIO */
		if (errno != EIO) {
			(void) fprintf(stderr, gettext("df: read error on "));
			perror(file);
			(void) fprintf(stderr, "bno = %ld\n", bno);
		} else {
			(void) fprintf(stderr, gettext(
"df: premature EOF on %s\n"), file);
			(void) fprintf(stderr,
			"bno = %ld expected = %d count = %d\n", bno, cnt, n);
		}
		return (0);
	}
	return (1);
}

char *
xmalloc(unsigned int size)
{
	char *ret;
	char *malloc();

	if ((ret = (char *)malloc(size)) == NULL) {
		(void) fprintf(stderr, gettext("umount: ran out of memory!\n"));
		exit(1);
	}
	return (ret);
}

struct mnttab *
mntdup(struct mnttab *mnt)
{
	struct mnttab *new;

	new = (struct mnttab *)xmalloc(sizeof (*new));

	new->mnt_special =
	    (char *)xmalloc((unsigned)(strlen(mnt->mnt_special) + 1));
	(void) strcpy(new->mnt_special, mnt->mnt_special);

	new->mnt_mountp =
	    (char *)xmalloc((unsigned)(strlen(mnt->mnt_mountp) + 1));
	(void) strcpy(new->mnt_mountp, mnt->mnt_mountp);

	new->mnt_fstype =
	    (char *)xmalloc((unsigned)(strlen(mnt->mnt_fstype) + 1));
	(void) strcpy(new->mnt_fstype, mnt->mnt_fstype);

	if (mnt->mnt_mntopts != NULL) {
		new->mnt_mntopts =
		    (char *)xmalloc((unsigned)(strlen(mnt->mnt_mntopts) + 1));
		(void) strcpy(new->mnt_mntopts, mnt->mnt_mntopts);
	} else {
		new->mnt_mntopts = NULL;
	}

#ifdef never
	new->mnt_freq = mnt->mnt_freq;
	new->mnt_passno = mnt->mnt_passno;
#endif /* never */

	return (new);
}

void
usage()
{

	(void) fprintf(stderr, gettext(
"ufs usage: df [generic options] [-o i] [directory | special]\n"));
	exit(1);
}

struct mntlist *
mkmntlist()
{
	FILE *mounted;
	struct mntlist *mntl;
	struct mntlist *mntst = NULL;
	struct extmnttab mnt;

	if ((mounted = fopen(MNTTAB, "r")) == NULL) {
		(void) fprintf(stderr, "df : ");
		perror(MNTTAB);
		exit(1);
	}
	resetmnttab(mounted);
	while (getextmntent(mounted, &mnt, sizeof (struct extmnttab)) == NULL) {
		mntl = (struct mntlist *)xmalloc(sizeof (*mntl));
		mntl->mntl_mnt = mntdup((struct mnttab *)(&mnt));
		mntl->mntl_next = mntst;
		mntl->mntl_devvalid = 1;
		mntl->mntl_dev = makedev(mnt.mnt_major, mnt.mnt_minor);
		mntst = mntl;
	}
	(void) fclose(mounted);
	return (mntst);
}

void
print_statvfs(struct statvfs64 *fs)
{
	int	i;

	for (i = 0; i < FSTYPSZ; i++)
		(void) printf("%c", fs->f_basetype[i]);
	(void) printf(" %7d %7lld %7lld",
	    fs->f_frsize,
	    fs->f_blocks,
	    fs->f_bavail);
	(void) printf(" %7lld %7lld %7d",
	    fs->f_files,
	    fs->f_ffree,
	    fs->f_fsid);
	(void) printf(" 0x%x ",
	    fs->f_flag);
	for (i = 0; i < 14; i++)
		(void) printf("%c",
		    (fs->f_fstr[i] == '\0') ? ' ' : fs->f_fstr[i]);
	printf("\n");
}

void
print_totals()
{
	/*
	 * TRANSLATION_NOTE
	 * Following string is used as a table header.
	 * Translated items should start at the same
	 * columns as the original items.
	 */
	(void) printf(gettext("Totals              %8lld %7lld %7lld"),
	    t_totalblks, t_used, t_avail);
	(void) printf("%6.0f%%\n",
	    (t_totalblks - t_reserved) == (fsblkcnt64_t)0 ?
	    0.0 :
	    (double)t_used / (double)(t_totalblks - t_reserved) * 100.0);
}

void
print_itotals()
{
	/*
	 * TRANSLATION_NOTE
	 * Following string is used as a table header.
	 * Translated items should start at the same
	 * columns as the original items.
	 */
	(void) printf(gettext("Totals              %8d %7d%6.0f%%\n"),
	    t_iused,
	    t_ifree,
	    t_inodes == 0 ? 0.0 : (double)t_iused / (double)t_inodes * 100.0);
}