/*
 *   Copyright (c) 1998 David Parsons. All rights reserved.
 *   
 *   Redistribution and use in source and binary forms, with or without
 *   modification, are permitted provided that the following conditions
 *   are met:
 *  1. Redistributions of source code must retain the above copyright
 *     notice, this list of conditions and the following disclaimer.
 *  2. Redistributions in binary form must reproduce the above copyright
 *     notice, this list of conditions and the following disclaimer in
 *     the documentation and/or other materials provided with the
 *     distribution.
 *  3. All advertising materials mentioning features or use of this
 *     software must display the following acknowledgement:
 *     
 *   This product includes software developed by David Parsons
 *   (orc@pell.chi.il.us)
 *
 *  4. My name may not be used to endorse or promote products derived
 *     from this software without specific prior written permission.
 *     
 *  THIS SOFTWARE IS PROVIDED BY DAVID PARSONS ``AS IS'' AND ANY
 *  EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
 *  THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A
 *  PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL DAVID
 *  PARSONS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
 *  EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED
 *  TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
 *  DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
 *  ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
 *  LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING
 *  IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
 *  THE POSSIBILITY OF SUCH DAMAGE.
 */
static const char rcsid[] = "Mastodon $Id: s.free.c 1.16 00/05/21 16:33:42-00:00 orc $";
/*
 * free() for Linux.
 */
#include <stdio.h>
#include <basis/options.h>
#include <string.h>
#include <malloc.h>
#include <sys/stat.h>
#include <unistd.h>

struct info {
    char *name;
    int class;
#define TOTAL	0x001
#define USED	0x002
#define FREE	0x004
#define PHYS	0x010
#define SWAP	0x020
#define SHARED	0x040
#define BUFFERS	0x080
#define CACHED	0x100
#define IFZERO	0x200
    unsigned long *rp;		/* points to a field to populate when building the report */
    unsigned long value;	/* what this field is worth */
    int set;			/* did we find something to put into this field? */
};

#define CELL	"%-10.10s"
#define RCELL	"%10.10s"

#define MEMINFO	"/proc/meminfo"

unsigned long total = 0;	/* total memory on the machine */
unsigned long unused = 0;	/* total memory unused on the machine */
unsigned long shared = 0;	/* memory shared between processes */
unsigned long buffers = 0;	/* memory used for buffers */
unsigned long cached  = 0;	/* memory used for caching */

/* New-style /proc/meminfo entries; one entry per line giving
 * the total for each category
 */
struct info info [] = {
	{"MemTotal:", TOTAL|PHYS|IFZERO, &total, 0 },
	{"MemFree:", FREE|PHYS, &unused, 0 },
	{"MemShared:", USED|PHYS|SHARED, &shared, 0 },
	{"Buffers:", FREE|PHYS|BUFFERS, &buffers, 0 },
	{"Cached:", USED|PHYS|CACHED, &cached, 0 },
	{"SwapTotal:", TOTAL|SWAP, &total, 0 },
	{"SwapFree:", FREE|SWAP, &unused, 0 },
};
#define NRINFO	(sizeof info / sizeof info[0])

/* The old-style /proc/meminfo entries come in a more snmpish form:
 * a header line describing the fields, then a couple of body lines,
 * one for physical memory (Mem:) and one for swap (Swap:) describing
 * how each is broken down (in bytes).  We parse this and use it for
 * a backup if none of the new-style entries are found.
 */
struct oldmap {
    char *name;
    unsigned long *value;
} oldmap[] = {
    { "total:",		&total },
    { "used:",		0  },	/* we ignore this one.. */
    { "free:",		&unused },
    { "shared:",	&shared },
    { "buffers:",	&buffers },
    { "cached:",	&cached },
};
#define NROLDMAP	(sizeof oldmap / sizeof oldmap[0])

char **titles;
int nrentries=0;
unsigned long *phys_ent;
unsigned long *swap_ent;


/*
 * memsize() prints out a number of kilobytes in fractions of a megabyte
 */
static enum { UNSPEC, BYTES, KILOBYTES, MEGABYTES } scale = KILOBYTES;
static char* scalenames[] = { "Size", "bytes", "Kbytes", "Mbytes" };

static void
memsize(unsigned long mem)
{
    char bfr[20];
    int scaling = scale;
    char sf = 0;

    if (scaling == UNSPEC) {
	if (mem < 1024)
	    scaling = BYTES;
	else if (mem < 1024*1024)
	    scaling = KILOBYTES;
	else
	    scaling = MEGABYTES;
    }

    switch (scaling) {
    default:
	sprintf(bfr, "%lu", mem);
	break;

    case KILOBYTES:
	sprintf(bfr, "%lu", mem >> 10);
	sf = 'K';
	break;
	
    case MEGABYTES:
	mem >>= 10;	/* scale down to kilobytes, then further divide */
			/* so we can have fractional precision */
	if (mem < 102)
	    strcpy(bfr, "0");
	else if (mem < 1024)
	    sprintf(bfr, "0.%d", (mem%1024)/102);
	else if ((mem%1024)/102 != 0)
	    sprintf(bfr, "%lu.%d", mem/1024, (mem%1024)/102);
	else
	    sprintf(bfr, "%lu", mem/1024);
	sf = 'M';
	break;
    }

    if (scale == UNSPEC && sf != 0) {
	bfr[strlen(bfr)+1] = 0;
	bfr[strlen(bfr)  ] = sf;
    }
    printf(RCELL, bfr);
} /* memsize */


/*
 * options that can be passed in to free
 */
enum cmds { oALL=1, oPHYS, oSWAP, oVERBOSE, oOLDFMT, oHUMAN, oHELP,
            oVERSION, oTOTAL, oDELAY, oK, oM, oB };

struct x_option options[] = {
    { oALL, 'a', "all", 0,		"Show both physical and virtual memory" },
    { oPHYS, 0, "physical", 0,		"Show physical memory" },
    { oSWAP, 0, "virtual", 0,		"Show virtual memory (swap space)" },
    { oVERBOSE, 'v', "verbose", 0,	"Show more details about memory allocation" },
    { oOLDFMT, 0, "old-kernel", 0,	"Read the old kernel /proc/meminfo format" },
    { oHUMAN, 'h', "human-readable", 0, "Show sizes in a more human readable form\n"
					"(eg: 12K, 34M, 5.6G)" },
    { oHELP, '?', "help", 0,		"Show this help message" },
    { oVERSION, 'V', "version", 0,	"Show this program's version number" },
    { oTOTAL, 't', "totals", 0,		"Show physical memory, swap, and totals" },
    { oDELAY, 's', "display-interval", "SECONDS", "Continuously display memory information,\n"
					"pausing SECONDS between displays" },
    { oM, 'm', "megabytes", 0,		"Show output in megabytes (the default)" },
    { oK, 'k', "kilobytes", 0,		"Show output in kilobytes" },
    { oB, 'b', "bytes", 0,		"Show output in bytes" },
};
#define NROPTIONS	(sizeof options/sizeof options[0])


/*
 * spit out a usage message, then quit
 */
static void
usage(char *pgm, int is_an_error)
{
    char *p;

    /* find the file part of the program name, so when we spit
     * out the usage message we don't include an unattractive
     * path component
     */
    if ( (p = strrchr(pgm, '/')) != 0)
	++p;
    else
	p = pgm;

    fprintf(stderr, "\nusage: %s [options]\n\n", p);
    showopts(stderr, NROPTIONS, options);

    exit (is_an_error ? 1 : 0);
} /* usage */


static void
accum(struct info *p)
{
    if ( *(p->rp) == 0 || (p->class & IFZERO) == 0 )
	*(p->rp) += p->value;
} /* accum */


main(int argc, char **argv)
{
    FILE* meminfo;
    char line[200];
    char mult[10];
    unsigned long tmp;
    int j, i, rc, opt;
    int phys = 0;	/* show physical memory */
    int swap = 0;	/* show swap area(s) */
    int verbose = 0;	/* show things in excrutiating detail */
    int usenewmap = 0;	/* default to using the old mappings */
    int forceold = 0;	/* force the old map (primarily debugging) */
    int showtotal = 0;	/* show memory totals */
    int delay = 0;	/* continuous output; redisplay every delay seconds */

    x_opterr = 1;

    while ((opt = x_getopt(argc, argv, NROPTIONS, options)) != EOF) {
	switch (opt) {
	case oDELAY:	delay = atoi(x_optarg);	break;
	case oM:	scale = MEGABYTES;	break;
	case oK:	scale = KILOBYTES;	break;
	case oB:	scale = BYTES;		break;
	case oHUMAN:	scale = UNSPEC;		break;
	case oVERBOSE:	verbose = 1;		break;
	case oPHYS:	phys = 1;		break;
	case oSWAP:	swap = 1;		break;
	case oTOTAL:	showtotal = 1;		break;
	case oALL:	phys = swap = 1;	break;
	case oOLDFMT:	forceold = 1;		break;
	case oVERSION:	puts("free $Revision: 1.16 $");
			exit(0);
	case oHELP:
	default:
			usage(argv[0], opt == oHELP);
	}
    }
    if (! (swap || phys || showtotal) )	/* default to showing swap and physical memory */
	swap = phys = 1;

    /* print column headers
     */
    if (swap || phys)
	printf(CELL, "");
    printf(RCELL, scalenames[scale]);
    printf(RCELL, "Used");
    if (verbose && (phys || swap == 0)) {
	printf(RCELL, "shared");
	printf(RCELL, "cached");
    }
    printf(RCELL, "Avail");
    if (verbose && (phys || swap == 0)) {
	printf(RCELL, "buffers");
	/*printf(RCELL, "free");*/
    }
    puts(" Capacity");

    titles = (char**)malloc(0);

    do {
	if ((meminfo = fopen(MEMINFO, "r")) == 0) {
	    fprintf(stderr, "free: ");
	    perror(MEMINFO);
	    exit(1);
	}

	while (fgets(line, sizeof line, meminfo) != 0) {
	    strtok(line, "\n");
	    for (i=0; i < NRINFO; i++)
	    if (strncasecmp(info[i].name, line, strlen(info[i].name)) == 0) {

		rc = sscanf(line+strlen(info[i].name), " %lu %s", &tmp, mult);

		if (rc > 0) {
		    if (strcasecmp(mult, "mb") == 0)
			tmp <<= 20;
		    else if (strcasecmp(mult, "kb") == 0)
			tmp <<= 10;
		    info[i].value = tmp;
		    info[i].set = 1;
		}
		break;
	    }
	    if (i >= NRINFO) {
		/* If we didn't match any of the known columns, we'll fill in the old
		 * compatability array for later perusal.
		 */
		unsigned long *result;
		char *p;

		/* The old compatability array contains a Mem: line */
		if (strncasecmp(line, "Mem:", 4) == 0)
		    result = phys_ent;
		/* a Swap: line to sum all the swapspace */
		else if (strncasecmp(line, "Swap:", 5) == 0)
		    result = swap_ent;
		/* and a blank-prefixed line with titles */
		else if (isspace(line[0])) {
		    /* hopefully this will be the first thing we see, so when we
		     * run into the rest of the old-style content we can properly
		     * populate it.
		     */
		    for (p = strtok(line, "\t "); p; p = strtok(0, "\t ")) {
			titles = realloc(titles, (1+nrentries) * sizeof *titles);
			titles[nrentries++] = strdup(p);
		    }
		    phys_ent = malloc(nrentries * sizeof *phys_ent);
		    swap_ent = malloc(nrentries * sizeof *swap_ent);
		    memset(phys_ent, 0, nrentries * sizeof *phys_ent);
		    memset(swap_ent, 0, nrentries * sizeof *swap_ent);
		    continue;
		}

		/* If we get here, we're populating either a Mem: or Swap: line,
		 * so result[] will point to the appropriate one and we can just
		 * walk down the line filling in results
		 */
		p = strtok(line, "\t ");
		for (i=0; i < nrentries; i++) {
		    if ((p = strtok(0, "\t ")) != 0)
			result[i] = atol(p);
		    else
			break;
		}
	    }
	}

	/* look over the new format entries and see if any are defined (if any
	 * are, we will use them instead of the more klunky old-style entries.)
	 */
	if (forceold == 0)
	    for (i=0; i<NRINFO; i++)
		if (info[i].set) {
		    usenewmap = 1;
		    break;
		}
	
	{
	    /* get total memory size from the size of
	     * /proc/kcore
	     */
	    struct stat kcore;

	    if (stat("/proc/kcore", &kcore) == 0)
		info[0].value = kcore.st_size;
	}


	/* print physical memory, if demanded
	 */
	if (phys) {
	    total = unused = 0;
	    cached = shared = buffers = 0;

	    if (usenewmap) {
		for (i = 0; i < NRINFO; i++)
		    if (info[i].class & PHYS)
			accum(&info[i]);
	    }
	    else {
		for (i=0; i < nrentries; i++)
		    for (j = 0; j < NROLDMAP; j++)
			if (strcasecmp(titles[i], oldmap[j].name) == 0)
			    if (oldmap[j].value)
				*(oldmap[j].value) += phys_ent[i];
	    }

	    unused += buffers;

	    if (total > 0) {
		printf(CELL, "Mem:");
		memsize(total);
		memsize(total-unused);
		if (verbose) {
		    memsize(shared);
		    memsize(cached);
		}
		memsize(unused);
		if (verbose) {
		    memsize(buffers);
		    /*memsize(unused-(buffers+cached));*/
		}
		printf(" %2d%%\n", (total-unused)/(total/102));
	    }
	}
	if (swap) {
	    total = unused = 0;
	    cached = shared = buffers = 0;

	    if (usenewmap) {
		for (i = 0; i < NRINFO; i++)
		    if (info[i].class & SWAP)
			accum(&info[i]);
	    }
	    else {
		for (i=0; i < nrentries; i++)
		    for (j = 0; j < NROLDMAP; j++)
			if (strcasecmp(titles[i], oldmap[j].name) == 0)
			    if (oldmap[j].value)
				*(oldmap[j].value) += swap_ent[i];
	    }

	    if (total > 0) {
		printf(CELL, "Swap:"); memsize(total); memsize(total-unused);
		if (verbose) {
		    printf(CELL, "");
		    printf(CELL, "");
		}
		memsize(unused);
		if (verbose) {
		    printf(CELL, "");
		    /*memsize(unused);*/
		}
		printf(" %2d%%\n", ((total-unused)*100)/total);
	    }
	}
	if ((swap == 0 && phys == 0) || showtotal) {
	    total = unused = 0;
	    cached = shared = buffers = 0;
	    if (usenewmap) {
		for (i = 0; i < NRINFO; i++)
		    accum(&info[i]);
	    }
	    else {
		for (i=0; i < nrentries; i++)
		    for (j = 0; j < NROLDMAP; j++)
			if (strcasecmp(titles[i], oldmap[j].name) == 0)
			    if (oldmap[j].value)
				*(oldmap[j].value) += phys_ent[i] + swap_ent[i];
	    }

	    unused += buffers;

	    if (total > 0) {
		if (showtotal && (phys || swap))
		    printf(CELL, "Total:");

		memsize(total);
		memsize(total-unused);
		if (verbose) {
		    memsize(shared);
		    memsize(cached);
		}
		memsize(unused);
		if (verbose) {
		    memsize(buffers);
		    /*memsize(unused-(buffers+cached));*/
		}
		printf(" %2d%%", (total-unused)/(total*102));
		putchar('\n');
	    }
	}
	if (delay) {
	    fclose(meminfo);
	    sleep(delay);
	    free(phys_ent);
	    free(swap_ent);
	}
    } while (delay > 0);
    exit(0);
}
