/*
 * free.c - display free memory information
 *
 * Copyright © 2011-2023 Jim Warner <james.warner@comcast.net>
 * Copyright © 2012-2023 Craig Small <csmall@dropbear.xyz>
 * Copyright © 2011-2012 Sami Kerola <kerolasa@iki.fi>
 * Copyright © 2004      Albert Cahalan
 * Copyright © 2002-2003 Robert Love <rml@tech9.net>
 * Copyright © 1992      Brian Edmonds and Rafal Maszkowski
 *
 * This program is free software; you can redistribute it and/or
 * modify it under the terms of the GNU General Public License
 * as published by the Free Software Foundation; either version 2
 * of the License, or (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
 */

#include <locale.h>
#include <errno.h>
#include <limits.h>
#include <ctype.h>
#include <getopt.h>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <wchar.h>

#include "config.h"
#include "c.h"
#include "nls.h"
#include "strutils.h"
#include "fileutils.h"

#include "meminfo.h"

#ifndef SIZE_MAX
#define SIZE_MAX		32
#endif

#define FREE_HUMANREADABLE	(1 << 1)
#define FREE_LOHI		(1 << 2)
#define FREE_WIDE		(1 << 3)
#define FREE_TOTAL		(1 << 4)
#define FREE_SI			(1 << 5)
#define FREE_REPEAT		(1 << 6)
#define FREE_REPEATCOUNT	(1 << 7)
#define FREE_COMMITTED		(1 << 8)

struct commandline_arguments {
	int exponent;		/* demanded in kilos, magas... */
	float repeat_interval;	/* delay in seconds */
	int repeat_counter;	/* number of repeats */
};

/* function prototypes */
static void usage(FILE * out);
double power(unsigned int base, unsigned int expo);
static const char *scale_size(unsigned long size, int flags, struct commandline_arguments args);

static void __attribute__ ((__noreturn__))
    usage(FILE * out)
{
        fputs(USAGE_HEADER, out);
	fprintf(out,
	      _(" %s [options]\n"), program_invocation_short_name);
	fputs(USAGE_OPTIONS, out);
	fputs(_(" -b, --bytes         show output in bytes\n"), out);
	fputs(_("     --kilo          show output in kilobytes\n"), out);
	fputs(_("     --mega          show output in megabytes\n"), out);
	fputs(_("     --giga          show output in gigabytes\n"), out);
	fputs(_("     --tera          show output in terabytes\n"), out);
	fputs(_("     --peta          show output in petabytes\n"), out);
	fputs(_(" -k, --kibi          show output in kibibytes\n"), out);
	fputs(_(" -m, --mebi          show output in mebibytes\n"), out);
	fputs(_(" -g, --gibi          show output in gibibytes\n"), out);
	fputs(_("     --tebi          show output in tebibytes\n"), out);
	fputs(_("     --pebi          show output in pebibytes\n"), out);
	fputs(_(" -h, --human         show human-readable output\n"), out);
	fputs(_("     --si            use powers of 1000 not 1024\n"), out);
	fputs(_(" -l, --lohi          show detailed low and high memory statistics\n"), out);
	fputs(_(" -t, --total         show total for RAM + swap\n"), out);
	fputs(_(" -v, --committed     show committed memory and commit limit\n"), out);
	fputs(_(" -s N, --seconds N   repeat printing every N seconds\n"), out);
	fputs(_(" -c N, --count N     repeat printing N times, then exit\n"), out);
	fputs(_(" -w, --wide          wide output\n"), out);
	fputs(USAGE_SEPARATOR, out);
	fputs(_("     --help     display this help and exit\n"), out);
	fputs(USAGE_VERSION, out);
	fprintf(out, USAGE_MAN_TAIL("free(1)"));

	exit(out == stderr ? EXIT_FAILURE : EXIT_SUCCESS);
}

double power(unsigned int base, unsigned int expo)
{
	return (expo == 0) ? 1 : base * power(base, expo - 1);
}

/* idea of this function is copied from top size scaling */
static const char *scale_size(unsigned long size, int flags, struct commandline_arguments args)
{
	static char up[] = { 'B', 'K', 'M', 'G', 'T', 'P', 0 };
	static char buf[BUFSIZ];
	int i;
	float base;
	long long bytes;

	base = (flags & FREE_SI) ? 1000.0 : 1024.0;
	bytes = size * 1024LL;

	if (!(flags & FREE_HUMANREADABLE)) {
		switch (args.exponent) {
		case 0:
			/* default output */
			snprintf(buf, sizeof(buf), "%ld", (long int)(bytes / (long long int)base));
			return buf;
		case 1:
			/* in bytes, which can not be in SI */
			snprintf(buf, sizeof(buf), "%lld", bytes);
			return buf;
		default:
			/* In desired scale. */
			snprintf(buf, sizeof(buf), "%ld",
			        (long)(bytes / power(base, args.exponent-1)));
			return buf;
		}
	}

	/* human readable output */
	if (4 >= snprintf(buf, sizeof(buf), "%lld%c", bytes, up[0]))
		return buf;

	for (i = 1; up[i] != 0; i++) {
		if (flags & FREE_SI) {
			if (4 >= snprintf(buf, sizeof(buf), "%.1f%c",
			                  (float)(bytes / power(base, i)), up[i]))
				return buf;
			if (4 >= snprintf(buf, sizeof(buf), "%ld%c",
			                  (long)(bytes / power(base, i)), up[i]))
				return buf;
		} else {
			if (5 >= snprintf(buf, sizeof(buf), "%.1f%ci",
			                  (float)(bytes / power(base, i)), up[i]))
				return buf;
			if (5 >= snprintf(buf, sizeof(buf), "%ld%ci",
			                  (long)(bytes / power(base, i)), up[i]))
				return buf;
		}
	}
	/*
	 * On system where there is more than exbibyte of memory or swap the
	 * output does not fit to column. For incoming few years this should
	 * not be a big problem (wrote at Apr, 2015).
	 */
	return buf;
}

static void check_unit_set(int *unit_set)
{
    if (*unit_set)
	xerrx(EXIT_FAILURE,
		_("Multiple unit options don't make sense."));
    *unit_set = 1;
}

/*
 * Print the header columns.
 * We cannot simply use the second printf because the length of the
 * translated strings doesn't work with it. Instead we need to find
 * the wide length of the string and use that.
 * This method also removes the messy wprintf/printf buffering issues
 */
#define HC_WIDTH 9
static void print_head_col(const char *str)
{
    int len;
    int spaces = 9;
    wchar_t wstr[BUFSIZ];

    len = mbstowcs(wstr, str, BUFSIZ);
    if (len < 0)
        spaces = 9;
    else if (len < HC_WIDTH) {
        int width;
        if ( (width = wcswidth(wstr, 99)) > 0)
            spaces = HC_WIDTH - width;
        else
            spaces = HC_WIDTH - len;
    } else
        spaces = 0;

    printf("%s%.*s", str, spaces, "         ");
}

int main(int argc, char **argv)
{
	int c, flags = 0, unit_set = 0, rc = 0;
	struct commandline_arguments args;
	struct meminfo_info *mem_info = NULL;

	/*
	 * For long options that have no equivalent short option, use a
	 * non-character as a pseudo short option, starting with CHAR_MAX + 1.
	 */
	enum {
		SI_OPTION = CHAR_MAX + 1,
		KILO_OPTION,
		MEGA_OPTION,
		GIGA_OPTION,
		TERA_OPTION,
		PETA_OPTION,
		TEBI_OPTION,
		PEBI_OPTION,
		HELP_OPTION
	};

	static const struct option longopts[] = {
		{  "bytes",	no_argument,	    NULL,  'b'		},
		{  "kilo",	no_argument,	    NULL,  KILO_OPTION	},
		{  "mega",	no_argument,	    NULL,  MEGA_OPTION	},
		{  "giga",	no_argument,	    NULL,  GIGA_OPTION	},
		{  "tera",	no_argument,	    NULL,  TERA_OPTION	},
		{  "peta",	no_argument,	    NULL,  PETA_OPTION	},
		{  "kibi",	no_argument,	    NULL,  'k'		},
		{  "mebi",	no_argument,	    NULL,  'm'		},
		{  "gibi",	no_argument,	    NULL,  'g'		},
		{  "tebi",	no_argument,	    NULL,  TEBI_OPTION	},
		{  "pebi",	no_argument,	    NULL,  PEBI_OPTION	},
		{  "human",	no_argument,	    NULL,  'h'		},
		{  "si",	no_argument,	    NULL,  SI_OPTION	},
		{  "lohi",	no_argument,	    NULL,  'l'		},
		{  "total",	no_argument,	    NULL,  't'		},
		{  "committed",	no_argument,	    NULL,  'v'		},
		{  "seconds",	required_argument,  NULL,  's'		},
		{  "count",	required_argument,  NULL,  'c'		},
		{  "wide",	no_argument,	    NULL,  'w'		},
		{  "help",	no_argument,	    NULL,  HELP_OPTION	},
		{  "version",	no_argument,	    NULL,  'V'		},
		{  NULL,	0,		    NULL,  0		}
	};

	/* defaults */
	args.exponent = 0;
	args.repeat_interval = 1000000;
	args.repeat_counter = 0;

#ifdef HAVE_PROGRAM_INVOCATION_NAME
	program_invocation_name = program_invocation_short_name;
#endif
	setlocale (LC_ALL, "");
	bindtextdomain(PACKAGE, LOCALEDIR);
	textdomain(PACKAGE);
	atexit(close_stdout);

	while ((c = getopt_long(argc, argv, "bkmghltvc:ws:V", longopts, NULL)) != -1)
		switch (c) {
		case 'b':
		        check_unit_set(&unit_set);
			args.exponent = 1;
			break;
		case 'k':
		        check_unit_set(&unit_set);
			args.exponent = 2;
			break;
		case 'm':
		        check_unit_set(&unit_set);
			args.exponent = 3;
			break;
		case 'g':
		        check_unit_set(&unit_set);
			args.exponent = 4;
			break;
		case TEBI_OPTION:
		        check_unit_set(&unit_set);
			args.exponent = 5;
			break;
		case PEBI_OPTION:
		        check_unit_set(&unit_set);
			args.exponent = 6;
			break;
		case KILO_OPTION:
		        check_unit_set(&unit_set);
			args.exponent = 2;
			flags |= FREE_SI;
			break;
		case MEGA_OPTION:
		        check_unit_set(&unit_set);
			args.exponent = 3;
			flags |= FREE_SI;
			break;
		case GIGA_OPTION:
		        check_unit_set(&unit_set);
			args.exponent = 4;
			flags |= FREE_SI;
			break;
		case TERA_OPTION:
		        check_unit_set(&unit_set);
			args.exponent = 5;
			flags |= FREE_SI;
			break;
		case PETA_OPTION:
		        check_unit_set(&unit_set);
			args.exponent = 6;
			flags |= FREE_SI;
			break;
		case 'h':
			flags |= FREE_HUMANREADABLE;
			break;
		case SI_OPTION:
			flags |= FREE_SI;
			break;
		case 'l':
			flags |= FREE_LOHI;
			break;
		case 't':
			flags |= FREE_TOTAL;
			break;
		case 'v':
			flags |= FREE_COMMITTED;
			break;
		case 's':
			flags |= FREE_REPEAT;
			errno = 0;
            args.repeat_interval = (1000000 * strtod_nol_or_err(optarg, "seconds argument failed"));
			if (args.repeat_interval < 1)
				xerrx(EXIT_FAILURE,
				     _("seconds argument `%s' is not positive number"), optarg);
			break;
		case 'c':
			flags |= FREE_REPEAT;
			flags |= FREE_REPEATCOUNT;
			args.repeat_counter = strtol_or_err(optarg,
				_("failed to parse count argument"));
			if (args.repeat_counter < 1)
			  error(EXIT_FAILURE, ERANGE,
				  _("failed to parse count argument: '%s'"), optarg);
			break;
		case 'w':
			flags |= FREE_WIDE;
			break;
		case HELP_OPTION:
			usage(stdout);
		case 'V':
			printf(PROCPS_NG_VERSION);
			exit(EXIT_SUCCESS);
		default:
			usage(stderr);
		}
	if (optind != argc)
	    usage(stderr);

	if ( (rc = procps_meminfo_new(&mem_info)) < 0)
    {
        if (rc == -ENOENT)
            xerrx(EXIT_FAILURE,
                  _("Memory information file /proc/meminfo does not exist"));
        else
            xerrx(EXIT_FAILURE,
                  _("Unable to create meminfo structure"));
    }
	do {
		/* Translation Hint: You can use 9 character words in
		 * the header, and the words need to be right align to
		 * beginning of a number. */
		if (flags & FREE_WIDE) {
			printf(_("               total        used        free      shared     buffers       cache   available"));
		} else {
			printf(_("               total        used        free      shared  buff/cache   available"));
		}
		printf("\n");
		print_head_col(_("Mem:"));
		printf("%11s", scale_size(MEMINFO_GET(mem_info, MEMINFO_MEM_TOTAL, ul_int), flags, args));
		printf(" %11s", scale_size(MEMINFO_GET(mem_info, MEMINFO_MEM_USED, ul_int), flags, args));
		printf(" %11s", scale_size(MEMINFO_GET(mem_info, MEMINFO_MEM_FREE, ul_int), flags, args));
		printf(" %11s", scale_size(MEMINFO_GET(mem_info, MEMINFO_MEM_SHARED, ul_int), flags, args));
		if (flags & FREE_WIDE) {
			printf(" %11s", scale_size(MEMINFO_GET(mem_info, MEMINFO_MEM_BUFFERS, ul_int),
				    flags, args));
			printf(" %11s", scale_size(MEMINFO_GET(mem_info, MEMINFO_MEM_CACHED_ALL, ul_int)
				    , flags, args));
		} else {
			printf(" %11s", scale_size(MEMINFO_GET(mem_info, MEMINFO_MEM_BUFFERS, ul_int) +
				    MEMINFO_GET(mem_info, MEMINFO_MEM_CACHED_ALL, ul_int), flags, args));
		}
		printf(" %11s", scale_size(MEMINFO_GET(mem_info, MEMINFO_MEM_AVAILABLE, ul_int), flags, args));
		printf("\n");
		/*
		 * 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 (flags & FREE_LOHI) {
			print_head_col(_("Low:"));
			printf("%11s", scale_size(MEMINFO_GET(mem_info, MEMINFO_MEM_LOW_TOTAL, ul_int), flags, args));
			printf(" %11s", scale_size(MEMINFO_GET(mem_info, MEMINFO_MEM_LOW_USED, ul_int), flags, args));
			printf(" %11s", scale_size(MEMINFO_GET(mem_info, MEMINFO_MEM_LOW_FREE, ul_int), flags, args));
			printf("\n");

			print_head_col( _("High:"));
			printf("%11s", scale_size(MEMINFO_GET(mem_info, MEMINFO_MEM_HIGH_TOTAL, ul_int), flags, args));
			printf(" %11s", scale_size(MEMINFO_GET(mem_info, MEMINFO_MEM_HIGH_USED, ul_int), flags, args));
			printf(" %11s", scale_size(MEMINFO_GET(mem_info, MEMINFO_MEM_HIGH_FREE, ul_int), flags, args));
			printf("\n");
		}

		print_head_col(_("Swap:"));
		printf("%11s", scale_size(MEMINFO_GET(mem_info, MEMINFO_SWAP_TOTAL, ul_int), flags, args));
		printf(" %11s", scale_size(MEMINFO_GET(mem_info, MEMINFO_SWAP_USED, ul_int), flags, args));
		printf(" %11s", scale_size(MEMINFO_GET(mem_info, MEMINFO_SWAP_FREE, ul_int), flags, args));
		printf("\n");

		if (flags & FREE_TOTAL) {
			print_head_col(_("Total:"));
			printf("%11s", scale_size(
				    MEMINFO_GET(mem_info, MEMINFO_MEM_TOTAL, ul_int) +
				    MEMINFO_GET(mem_info, MEMINFO_SWAP_TOTAL, ul_int), flags, args));
			printf(" %11s", scale_size(
				    MEMINFO_GET(mem_info, MEMINFO_MEM_USED, ul_int) +
				    MEMINFO_GET(mem_info, MEMINFO_SWAP_USED, ul_int), flags, args));
			printf(" %11s", scale_size(
				    MEMINFO_GET(mem_info, MEMINFO_MEM_FREE, ul_int) +
				    MEMINFO_GET(mem_info, MEMINFO_SWAP_FREE, ul_int), flags, args));
			printf("\n");
		}
		if (flags & FREE_COMMITTED) {
			print_head_col(_("Comm:"));
			printf("%11s", scale_size(MEMINFO_GET(mem_info, MEMINFO_MEM_COMMIT_LIMIT, ul_int), flags, args));
			printf(" %11s", scale_size(MEMINFO_GET(mem_info, MEMINFO_MEM_COMMITTED_AS, ul_int), flags, args));
			printf(" %11s", scale_size(
				    MEMINFO_GET(mem_info, MEMINFO_MEM_COMMIT_LIMIT, ul_int) -
				    MEMINFO_GET(mem_info, MEMINFO_MEM_COMMITTED_AS, ul_int), flags, args));
			printf("\n");
		}

		fflush(stdout);
		if (flags & FREE_REPEATCOUNT) {
			args.repeat_counter--;
			if (args.repeat_counter < 1)
				exit(EXIT_SUCCESS);
		}
		if (flags & FREE_REPEAT) {
			printf("\n");
			usleep(args.repeat_interval);
		}
	} while ((flags & FREE_REPEAT));

	exit(EXIT_SUCCESS);
}
