/* fihig.c
 *
 * Create a histogram of file sizes of a given directory. A directory may be
 * omitted and in that case the current directory is used.
 *
 * Usage: fihig [dir]
 *
 * Tabstop = 8
 */

/***** INCLUDES ***************************************************************/
#include <stdio.h>
#include <errno.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <ftw.h>

/***** DEFINES ****************************************************************/
#define K(x)	(x*1024)	/* kB */
#define M(x)	(x*K(1)*K(1))	/* MB */

/***** GLOBAL VARIABLES *******************************************************/
char *cwd = "./";
int  errno;

/* Array which contains a division of file sizes and the associated counting.
 * Row 0 is the size division, row 1 holds the counting. Note row 1 has one
 * element more than row 0. This extra element holds the counting of sizes
 * larger than the largest size division.
 *
 * -1 marks the end of the division list.
 */
long size_histo[][12] = {
	{K(1), K(2), K(4), K(8), K(16), K(32), K(64), K(128), K(256), K(512), M(1), -1},
	{   0,    0,    0,    0,     0,     0,     0,      0,      0,      0,    0,  0}
};

/***** PROTOTYPES *************************************************************/
int getfsize(char *, struct stat *, int);

/***** FUNCTIONS **************************************************************/
int main(int argc, char *argv[])
{
	char	*dir;
	int	i;
	long	lower_range, total_files;
	float	pct;
	
	if (argc <= 1) dir = cwd;
	else dir = argv[1];
	
	printf("Creating file size histogram of %s ...\n", dir);
	if (ftw(dir, getfsize, 2)) {
		perror("ftw()");
		return (1);
	}

	i = total_files = 0;
	do {
		total_files += size_histo[1][i];
	} while (size_histo[0][i++] != -1);
	
	lower_range = 0;
	for (i = 0; size_histo[0][i] != -1; i++) {
		pct = 100 * (float) size_histo[1][i] / total_files;
		printf("%5ldk < fsz <= %5ldk : %4.1f%% (%ld)\n", lower_range/1024,
			size_histo[0][i]/1024, pct, size_histo[1][i]);
		lower_range = size_histo[0][i];
	}
	
	/* Also print counting of larger than largest size division. */
	pct = 100 * (float) size_histo[1][i] / total_files;
	printf("         fsz >  %5ldk : %4.1f%% (%ld)\n", lower_range/1024, pct,
		size_histo[1][i]);
	
	printf("Total number of files is %ld.\n", total_files);
	
	return (0);
}

/* Determine the size of the file the function ftw() has found.
 * Only a regular file or a symbolic link is processed, in other cases the
 * function returns immediately.
 *
 * Input:
 *	file, pathname of file ftw() found;
 *	sb, pointer to 'stat' struct of file 'file';
 *	flag, status of ftw() call;
 *
 * Output:
 *	always 0;
 */
int getfsize(char *file, struct stat *sb, int flag)
{
	int	i;
	long	fsz, lower_range;
	
	/* If the stat call failed, print error and return. */
	if (flag == FTW_NS || flag == FTW_DNR) {
		perror("getfsize()");
		return (0);
	}
	
	/* Ignore directories too. */
	if (flag == FTW_D) return (0);
	
	/* Compare the file size to the size ranges in the size_histo array. */
	fsz = sb->st_size;
	lower_range = 0;
	for (i = 0; size_histo[0][i] != -1; i++) {
		if (fsz > lower_range && fsz <= size_histo[0][i]) {
			++size_histo[1][i];
			return (0);
		}
		lower_range = size_histo[0][i];
	}
	
	/* File size was not part of the size division, defined in the array,
	 * so it's counted as something larger than the largest size division.
	 * The test is probably unnecessary in practice, unless the file size
	 * is negative.
	 */
	if (fsz > lower_range) ++size_histo[1][i];
	
	return (0);
}
