/*
 * free.c - free(1)
 * utility to display free memory information via /proc/meminfo, part of procps
 *
 * All new, Robert Love <rml@tech9.net>		18 Nov 2002
 * Original version by Brian Edmonds		14 Dec 1992
 *
 * This program is licensed under the GNU General Public License, v2
 *
 * Copyright (C) 2003 Robert Love
 */

#include <stdlib.h>
#include <stdio.h>
#include <unistd.h>
#include <fcntl.h>
#include <errno.h>
#include <getopt.h>

#include "proc/sysinfo.h"
#include "proc/version.h"

void show_usage(const char *cmd)
{
	fprintf(stderr, "usage: %s "
		"[-b|-k|-m|-g] [-l] [-o] [-t] [-s delay] [-c count] [-V]\n",
		cmd);
	fprintf(stderr,"\t-b,-k,-m,-g show output in bytes, KB, MB, or GB\n");
	fprintf(stderr,"\t-l show detailed low and high memory statistics\n");
	fprintf(stderr,"\t-o use old format (no -/+buffers/cache line)\n");
	fprintf(stderr,"\t-t display total for RAM + swap\n");
	fprintf(stderr,"\t-s update every [delay] seconds\n");
	fprintf(stderr,"\t-c update [count] times\n");
	fprintf(stderr,"\t-V display version information and exit\n");
}

int main(int argc, char *argv[])
{
	int i, count = 0, shift = 10, pause_length = 0;
	int show_high = 0, show_total = 0, old_format = 0;

	struct option longopts[] = {
		{ "bytes",	0,	NULL,	'b' },
		{ "kb",		0,	NULL,	'k' },
		{ "mb",		0,	NULL,	'm' },
		{ "gb",		0,	NULL,	'g' },
		{ "lowhigh",	0,	NULL,	'l' },
		{ "old",	0,	NULL,	'o' },
		{ "totals",	0,	NULL,	't' },
		{ "count",	1,	NULL,	'c' },
		{ "repeat",	1,	NULL,	's' },
		{ "version",	0,	NULL,	'V' },
		{ "help",	0,	NULL,	'h' },
		{ NULL,		0,	NULL,	 0  }
	};

	while ((i = getopt_long(argc, argv, "bkmglotc:s:tV",
				longopts, NULL)) != -1) {
		switch (i) {
			case 'b':
				shift = 0;
				break;
			case 'k':
				shift = 10;
				break;
			case 'm':
				shift = 20;
				break;
			case 'g':
				shift = 30;
				break;
			case 'l':
				show_high = 1;
				break;
			case 'o':
				old_format = 1;
				break;
			case 't':
				show_total = 1;
				break;
			case 's':
				errno = 0;
				pause_length = 1000000;
				pause_length *= strtoul(optarg, NULL, 10);
				if (errno) {
					perror("strtoul");
					return 1;
				}
				break;
			case 'c':
				errno = 0;
				count = strtoul(optarg, NULL, 10);
				if (errno) {
					perror("strtoul");
					return 1;
				}
				break;
			case 'V':
				display_version();
				return 0;
			case 'h':
				show_usage(argv[0]);
				return 0;
			default:
				show_usage(argv[0]);
				return 1;
		}
	}

	do {
		struct meminfo_struct mem_info = MEMINFO_ZERO;

		meminfo(&mem_info);
		if (mem_info.mem.total == 0) {
			fprintf(stderr,
			  "cannot get size of memory from " MEMINFO_FILE "\n");
			return 1;
		}

		printf("%18s %10s %10s %10s %10s %10s\n",
			"total", "used", "free", "shared", "buffers", "cached");

		printf("%-7s %10llu %10llu %10llu %10llu %10llu %10llu\n",
			"Mem:", mem_info.mem.total >> shift,
			mem_info.mem.used >> shift, mem_info.mem.free >> shift,
			mem_info.mem.shared >> shift,
			mem_info.mem.buffers >> shift,
			mem_info.mem.cached >> shift);

		/*
		 * Print low vs. high information, if the user requested it.
		 * Note we check if low_total==0: if so, then this kernel does
		 * not export the low and high stats.  Note we still want to
		 * print the high info, even if it is zero.
		 */
		if (show_high && mem_info.mem.low_total != 0) {
			printf("%-7s %10llu %10llu %10llu %10llu %10llu "
				"%10llu\n", "Low:",
				mem_info.mem.low_total >> shift,
				mem_info.mem.low_used >> shift,
				mem_info.mem.low_free >> shift, 0LL, 0LL, 0LL);
			printf("%-7s %10llu %10llu %10llu %10llu %10llu "
				"%10llu\n", "High:",
				mem_info.mem.high_total >> shift,
				mem_info.mem.high_used >> shift,
				mem_info.mem.high_free >> shift, 0LL, 0LL, 0LL);
		}

		if (!old_format)
			printf("-/+ buffers/cache: %10llu %10llu\n",
				(mem_info.mem.used - mem_info.mem.buffers -
				 mem_info.mem.cached) >> shift,
				(mem_info.mem.free + mem_info.mem.buffers +
				 mem_info.mem.cached) >> shift);

		printf("%-7s %10llu %10llu %10llu\n", "Swap:",
			mem_info.swap.total >> shift,
			mem_info.swap.used >> shift,
			mem_info.swap.free >> shift);

		if (show_total)
			printf("%-7s %10llu %10llu %10llu\n", "Total:",
				(mem_info.mem.total + mem_info.swap.total)
					>> shift,
				(mem_info.mem.used + mem_info.swap.used)
					>> shift,
				(mem_info.mem.free + mem_info.swap.free)
					>> shift);

		if (pause_length) {
			fputc('\n', stdout);
			fflush(stdout);
			if (count != 1)
				usleep(pause_length);
		}
	} while (pause_length && --count);

	return 0;
}
