/* Engin AYDOGAN 2004
 * This code tries to reset or at least invalidate linux cache and buffers */
 
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>
 
/* size of the chunk we are going to malloc in each iteration */
#define CHUNK (1024*1024)
#define MAX_CHUNK (1024*1024)
 
typedef struct mem_struct_t
{
	long int MemFree;
	long int Cached;
	long int Buffers;
} MemStruct;
 
MemStruct parse_meminfo()
{
	/* to read /proc/meminfo */
	FILE* meminfo;
	MemStruct m;
	char buffer[100] = {0};
	char* end;
	int found = 0;
	
	/* Try to read /proc/meminfo, bail out if fails */
	if( !( meminfo = fopen( "/proc/meminfo", "r" ) ) )
	{
		fprintf( stderr, "Could not open /proc/meminfo [%s]\n",
				 strerror( errno ) );
		fprintf( stderr, "You should explicitly tell the "\
				 "size of memory to be freed.\n" );
		exit( EXIT_FAILURE );
	}
 
	/* Read each line untill we got all we ned */
	while( fgets( buffer, sizeof( buffer ), meminfo ) )
	{
		/* MemFree is the current free memory */
		if( strstr( buffer, "MemFree:" ) == buffer )
		{
			m.MemFree = strtol( buffer + 8, &end, 10 );
			found++;
		}
		/* Buffers */
		else if( strstr( buffer, "Buffers:" ) == buffer )
		{
			m.Buffers = strtol( buffer + 8, &end, 10 );
			found++;
		}
		/* Cached */
		else if( strstr( buffer, "Cached:" ) == buffer )
		{
			m.Cached = strtol( buffer + 8, &end, 10 );
			found++;
			break;
		}
	}
	fclose(meminfo);
	/* Loosly check if we got all we need */
	if( found != 3 )
	{
		fprintf( stderr, "Could not parse /proc/meminfo\n" );
		exit( EXIT_FAILURE );
	}
	return m;
}
 
int main( int argc, char** argv )
{
	/* defaults to megabytes */
	int k = 1024 * 1024;
	/* printed characters for percentage */
	int p = 0;
	/* garbage data set by strtol */
	char* end;
	/* r = bytes to be freed */
	long int r;
	/* allocated chunks array, holds up to MAX_CHUNK chunks of CHUNK bytes  */
	void* g[MAX_CHUNK] = {0};
	/* freed bytes so far */
	int freed = 0;
	/* bytes to be freed in current iteration */
	int tobefreed = 0;
	/* /proc/meminfo info */
	MemStruct m;
	/* index of allocated memory spaces array */
	int i = 0;
	/* Any paramter given ? */
	if( argc > 1 )
	{
		/* Help request, print some information */
		if( !strcmp( argv[1], "-h" ) || !strcmp( argv[1], "--help" ) )
		{
			fprintf( stderr, "%s [size[M|k]]\n", argv[0] );
			return EXIT_FAILURE;
		}
		
		/* Try to read the parameter which is supposed to be a number
 		 * followed by an optionail size specifier which is either k or m.
 		 * Both are case insensitive */
		r = strtol( argv[1], &end, 10 );
		
		/* Error check as anyone sane would do */
		if( errno )
		{
			fprintf( stderr, "Could not parse parameter '%s'. Error was '%s'\n", 
							 argv[1], strerror( errno ) );
			return EXIT_FAILURE;
		}
		
		/* Negative value is simply nonsense, how am I supposed to free -10M */
		if( r < 0 )
		{
			fprintf( stderr, "Please provide a positive size.\n" );
			return EXIT_FAILURE;
		}
		
		/* Give feedback that there is some ignored garbage in the paramter */
		if( strlen(end) > 1 )
		{
			fprintf( stderr, "Ignoring garbage input '%s'\n", end+1 );
		}
		
		/* Check for the size specifier */
		if( end[0] == '\0' || end[0] == 'M' || end[0] == 'm' )
		{
			/* Either there's no size specifier or it's m or M.
 			 * I'm leaving k as untouched, since its default value
 			 * is 1024*1024 which is M. */			 
			fprintf( stderr, "Freeing %ldM of memory...\n", r );
		}
		else if( end[0] == 'K' || end[0] == 'k' )
		{
			k = 1024;
			fprintf( stderr, "Freeing %ldk of memory...\n", r );
		}
		else
		{
			fprintf( stderr, "Unknown size specifier ('%c'), " \
							 "defaulting to M\n", end[0] );
			fprintf( stderr, "Freeing %ldM of memory...\n", r );
		}
	}
	else
	{
		/* No argument is given. Try to calculate cache+buffer+free mem
 		 * and free that to naively force of to flush the cache and buffer */
		fprintf( stderr, "Trying to free buffer and cache...\n" );
 
		m = parse_meminfo();
		r = m.MemFree + m.Cached + m.Buffers;
		k = 1024;
		
		fprintf( stderr, "Before -> MemFree: %ldk Cached: %ldk Buffers: %ldk Total: %ldk\n",
						m.MemFree, m.Cached, m.Buffers, r );
	}
	
	/* How many bytes to allocate and reset */
	r *= k;
	while( freed < r )
	{
		/* How many bytes should we process in this iteration.
 		 * Process whole CHUNK of bytes if we are not exceeding
 		 * our total bytes to be reset. If we're process as few
 		 * as necessary. */
		tobefreed = (freed + CHUNK) > r ? r - freed : CHUNK;
 
		/* Actually allocate the space and bail if we fail at that */
		if( !( g[i] = malloc( tobefreed ) ) )
		{
			fprintf( stderr, "Could not allocate memory\n" );
			return EXIT_FAILURE;
		}
 
		/* This is the actual palce where we're allocating and
 		 * resetting this memory space. malloc itself alone just
 		 * allocates the space, does not occupies it. */
		memset( g[i++], 0, tobefreed );
 
		freed += tobefreed;
 
		/* Print visual feedback of how much we progressed */		
		while( p-- ) printf( "\b" );
		p = printf( "%.2f%%", ((freed*1.0)/(r))*100);
		fflush( stdout );
	}
	while(i--)
	{
		free(g[i]);
	}
	printf( "\n" );
	m = parse_meminfo();
	fprintf( stderr, "After  -> MemFree: %ldk Cached: %ldk Buffers: %ldk\n",
					 m.MemFree, m.Cached, m.Buffers);
	return EXIT_SUCCESS;
}