/* NAME:
 *	spacefor - indicate if sufficient free space available
 *
 * SYNOPSIS:
 *	spacefor "path" ["want"]
 *	
 *	size_t spacefor(const char *"path", const char *"want")
 *	size_t space_on(const char *"path", int "uid")
 *	size_t space_for(const char *"path", size_t "Kbytes")
 *	size_t space_for_bytes(const char *"path", size_t "bytes")
 *	size_t space_for_file(const char *"path", const char *"file")
 *	
 * DESCRIPTION:
 *	The command 'spacefor' calls either spacefor("path", "want")
 *	or space_on("path", -1) depending on whether "want" is specified.
 *
 *	If "want" does not represent a number, spacefor() calls
 *	space_for_file() passing "want" for "file".
 *	Otherwise, "want" is assumed to be a number of bytes (unless
 *	it ends with 'K','G' or 'M') which is converted to an appropriate
 *	"Kbytes" value to pass to space_for().
 *	
 *	space_on() returns the free space in Kbytes available to "uid".
 *	If "uid" is < 0, we use geteuid(2).  If "uid" is 0, then we
 *	use f_bfree in our calculations rather than f_bavail.
 *	For some OS's we work out what system call to use (default is
 *	statfs(2)) but it can be controlled by defining STATFS_F and
 *	if the struct has a different name defining STATFS_ST.
 *
 *	space_for() returns < 0 on failure (whatever statfs(2)
 *	returned), otherwise whether there is room for "Kbytes".
 *
 *	space_for_bytes() returns < 0 on failure (whatever statfs(2)
 *	returned), otherwise whether there is room for "bytes".
 *
 *	space_for_file() returns < 0 if stat(2) fails, otherwise it
 *	simply passes the st_size (converted to Kbytes) to space_for().
 *
 * BUGS:
 *	Working in Kbytes, avoids overflow problems when filesystems
 *	are bigger than 4G.
 *
 * AUTHOR:
 *	Simon J. Gerraty <sjg@quick.com.au>
 */
/*
 *      @(#)Copyright (c) 1998, Simon J. Gerraty.
 *      
 *      This code is presented AS IS.  It comes with NO WARRANTY.
 *      Permission to use, modify and distribute this source code 
 *      is granted subject to the following conditions.
 *      1/ that the above copyright notice and this notice 
 *      are preserved in all copies and that due credit be given 
 *      to the author.  
 *      2/ that any changes to this code are clearly commented 
 *      as such so that the author does not get blamed for bugs 
 *      other than his own.
 *      
 *      Please send copies of changes and bug-fixes to:
 *      sjg@quick.com.au
 */
#ifndef lint
static char RCSid[] = "$Id: spacefor.c,v 1.6 1998/08/20 12:24:35 sjg Exp $";
#endif
#ifdef HAVE_CONFIG_H
# include "config.h"
#endif
#include <stdio.h>
#include <sys/types.h>
#include <sys/param.h>
#include <sys/stat.h>
#if defined(BSD4_4)
# include <sys/mount.h>
#elif defined(HAVE_SYS_STATVFS_H) || (defined(sun) && defined(__svr4__))
# include <sys/statvfs.h>
# define STATFS_F statvfs
# define F_BSIZE f_frsize
#elif defined(HAVE_SYS_VFS_H) || defined(sun)
# include <sys/vfs.h>
#endif

/*
 * these are correct for most systems
 */
#ifndef STATFS_F
# define STATFS_F statfs
#endif
#ifndef STATFS_ST
# define STATFS_ST STATFS_F
#endif
#ifndef F_BSIZE
# define F_BSIZE f_bsize
#endif
#ifndef F_BFREE
# define F_BFREE f_bfree
#endif
#ifndef F_BAVAIL
# define F_BAVAIL f_bavail
#endif

#define B2K(x) ((x + 1023) / 1024)


size_t
space_on(path, uid)
     const char *path;
     int uid;
{
	size_t ret;
	struct STATFS_ST sb;

	if (uid < 0)
		uid = geteuid();
	if ((ret = STATFS_F(path, &sb)) == 0) {
		if (uid == 0) {
			ret = sb.F_BFREE;
		} else {
			ret = sb.F_BAVAIL;
		}
		if (sb.F_BSIZE >= 1024)
			sb.F_BSIZE /= 1024; /* will be an even multiple */
		else
			ret /= 1024;	/* better err on side of too small */
		ret *= sb.F_BSIZE;
	}
	return ret;
}

size_t
space_for(path, want)
     const char *path;
     size_t want;
{
	size_t have = space_on(path, -1);

	if (have == (size_t) -1)
		return have;
	return (have > want);
}

size_t
space_for_bytes(path, want)
     const char *path;
     size_t want;
{
	return space_for(path, B2K(want));
}

size_t
space_for_file(path, file)
     const char *path;
     const char *file;
{
	struct stat sb;
	size_t ret = -2;

	if (stat(file, &sb) == 0) {
		ret = space_for(path, B2K(sb.st_size));
	}
	return ret;
}

size_t
spacefor(path, want)
	const char *path;
	const char *want;
{
	size_t Kbytes;
	size_t ret = -4;
	
	if ((Kbytes = atoi(want)) == 0) {
		ret = space_for_file(path, want);
	} else {
		switch (want[strlen(want) - 1] & ~0x20) {
		case 'G': Kbytes *= 1024; /* FALL THROUGH */
		case 'M': Kbytes *= 1024; /* FALL THROUGH */
		case 'K': break;
		default: Kbytes = B2K(Kbytes); break;
		}
		if (Kbytes > 0)
			ret = space_for(path, Kbytes);
	}
	return ret;
}

#ifdef MAIN
int
main(argc, argv)
	int	argc;
	char	**argv;
{
	size_t ret;
	int x;
	
	if (argc < 3) {
		if (argc > 1) {
			if ((ret = space_on(argv[1], -1)) == (size_t) -1) {
				perror(argv[1]);
				exit(1);
			}
			printf("%lu\n", ret);
			exit(0);
		} else {
			fprintf(stderr,
				"usage\n\t%s 'path' 'want'[{K,M,G}]\n", argv[0]);
			exit(1);
		}
	}
	ret = spacefor(argv[1], argv[2]);
	if ((x = (int) ret) < 0) {
		fprintf(stderr, "%s: ", argv[0]);
		switch(x) {
		case -1:
			perror(argv[1]);
			break;
		case -2:
			perror(argv[2]);
			break;
		case -4:
			fprintf(stderr, "%s probably too big\n", argv[2]);
			break;
		}
	}
	exit (x <= 0);		/* 0 means true to the shell */
}
#endif
