/*
 * slab.c - slab related functions for libproc
 *
 * Chris Rivera <cmrivera@ufl.edu>
 * Robert Love <rml@tech9.net>
 *
 * Copyright (C) 2003 Chris Rivera
 * Copyright 2004, Albert Cahalan
 *
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Lesser General Public
 * License as published by the Free Software Foundation; either
 * version 2.1 of the License, or (at your option) any later version.
 *
 * This library 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
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with this library; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA
 */

#include <stdlib.h>
#include <stdio.h>
#include <unistd.h>
#include <limits.h>
#include <ctype.h>

#include "slab.h"
#include "procps.h"
#include "alloc.h"

#define SLABINFO_LINE_LEN	2048
#define SLABINFO_VER_LEN	100
#define SLABINFO_FILE		"/proc/slabinfo"

static struct slab_info *free_index;

/*
 * get_slabnode - allocate slab_info structures using a free list
 *
 * In the fast path, we simply return a node off the free list.  In the slow
 * list, we malloc() a new node.  The free list is never automatically reaped,
 * both for simplicity and because the number of slab caches is fairly
 * constant.
 */
static struct slab_info *get_slabnode(void)
{
	static const struct slab_info initializer;
	struct slab_info *node;

	if (free_index) {
		node = free_index;
		free_index = free_index->next;
	} else {
		node = xmalloc(sizeof(struct slab_info));
	}
	*node = initializer;
	return node;
}

/*
 * slab_badname_detect - return true if current slab was declared with
 *                       whitespaces for instance
 *			 FIXME :Other cases ?
 */

static int slab_badname_detect(const char *restrict buffer)
{
	int numberarea=0;
	while (*buffer){
		if((*buffer)==' ')
			numberarea=1;
		if(isalpha(*buffer)&&numberarea)
			return 1;
		buffer++;
	}
	return 0;
}

/*
 * put_slabinfo - return all allocated nodes to the free list
 */
void put_slabinfo(struct slab_info *head)
{
	free_index = head;
}

/*
 * free_slabinfo - deallocate the memory associated with each node in the
 * slab_info linked list
 */
void free_slabinfo(struct slab_info *list)
{
	while (list) {
		struct slab_info *temp = list->next;
		free(list);
		list = temp;
	}
}

/* parse_slabinfo20 - actual parse routine for slabinfo 2.x (2.6 kernels)
 * Note: difference between 2.0 and 2.1 is in the ": globalstat" part where version 2.1
 * has extra column <nodeallocs>. We don't use ": globalstat" part in both versions.
 *
 * Formats (we don't use "statistics" extensions)
 *
 *  slabinfo - version: 2.1
 *  # name            <active_objs> <num_objs> <objsize> <objperslab> <pagesperslab> \
 *  : tunables <batchcount> <limit> <sharedfactor> \
 *  : slabdata <active_slabs> <num_slabs> <sharedavail>
 *
 *  slabinfo - version: 2.1 (statistics)
 *  # name            <active_objs> <num_objs> <objsize> <objperslab> <pagesperslab> \
 *  : tunables <batchcount> <limit> <sharedfactor> \
 *  : slabdata <active_slabs> <num_slabs> <sharedavail> \
 *  : globalstat <listallocs> <maxobjs> <grown> <reaped> <error> <maxfreeable> <freelimit> <nodeallocs> \
 *  : cpustat <allochit> <allocmiss> <freehit> <freemiss>
 *
 *  slabinfo - version: 2.0
 *  # name            <active_objs> <num_objs> <objsize> <objperslab> <pagesperslab> \
 *  : tunables <batchcount> <limit> <sharedfactor> \
 *  : slabdata <active_slabs> <num_slabs> <sharedavail>
 *
 *  slabinfo - version: 2.0 (statistics)
 *  # name            <active_objs> <num_objs> <objsize> <objperslab> <pagesperslab> \
 *  : tunables <batchcount> <limit> <sharedfactor> \
 *  : slabdata <active_slabs> <num_slabs> <sharedavail> \
 *  : globalstat <listallocs> <maxobjs> <grown> <reaped> <error> <maxfreeable> <freelimit> \
 *  : cpustat <allochit> <allocmiss> <freehit> <freemiss>
 */
static int parse_slabinfo20(struct slab_info **list, struct slab_stat *stats,
				FILE *f)
{
	struct slab_info *curr = NULL, *prev = NULL;
	char buffer[SLABINFO_LINE_LEN];
	int entries = 0;
	int page_size = getpagesize();

	stats->min_obj_size = INT_MAX;
	stats->max_obj_size = 0;

	while (fgets(buffer, SLABINFO_LINE_LEN, f)) {
		int assigned;

		if (buffer[0] == '#')
			continue;

		curr = get_slabnode();
		if (!curr)
			break;

		if (entries++ == 0)
			*list = curr;
		else
			if (prev)
				prev->next = curr;

		assigned = sscanf(buffer, "%" STRINGIFY(SLAB_INFO_NAME_LEN)
				"s %d %d %d %d %d : tunables %*d %*d %*d : \
				slabdata %d %d %*d", curr->name,
				&curr->nr_active_objs, &curr->nr_objs,
				&curr->obj_size, &curr->objs_per_slab,
				&curr->pages_per_slab, &curr->nr_active_slabs,
				&curr->nr_slabs);

		if (assigned < 8) {
			fprintf(stderr, "unrecognizable data in slabinfo!\n");
			curr = NULL;
			break;
		}

		if (curr->obj_size < stats->min_obj_size)
			stats->min_obj_size = curr->obj_size;
		if (curr->obj_size > stats->max_obj_size)
			stats->max_obj_size = curr->obj_size;

		curr->cache_size = (unsigned long)curr->nr_slabs * curr->pages_per_slab * page_size;

		if (curr->nr_objs) {
			curr->use = 100 * (float)curr->nr_active_objs / curr->nr_objs;
			stats->nr_active_caches++;
		} else
			curr->use = 0;

		stats->nr_objs += curr->nr_objs;
		stats->nr_active_objs += curr->nr_active_objs;
		stats->total_size += (unsigned long)curr->nr_objs * curr->obj_size;
		stats->active_size += (unsigned long)curr->nr_active_objs * curr->obj_size;
		stats->nr_pages += curr->nr_slabs * curr->pages_per_slab;
		stats->nr_slabs += curr->nr_slabs;
		stats->nr_active_slabs += curr->nr_active_slabs;

		prev = curr;
	}

	if (!curr) {
		fprintf(stderr, "\rerror reading slabinfo!\n");
		return 1;
	}

	curr->next = NULL;
	stats->nr_caches = entries;
	if (stats->nr_objs)
		stats->avg_obj_size = stats->total_size / stats->nr_objs;

	return 0;
}

/*
 * parse_slabinfo11 - actual parsing routine for slabinfo 1.1 (2.4 kernels)
 */
static int parse_slabinfo11(struct slab_info **list, struct slab_stat *stats,
				FILE *f)
{
	struct slab_info *curr = NULL, *prev = NULL;
	char buffer[SLABINFO_LINE_LEN];
	int entries = 0;
	int page_size = getpagesize();

	stats->min_obj_size = INT_MAX;
	stats->max_obj_size = 0;

	while (fgets(buffer, SLABINFO_LINE_LEN, f)) {
		int assigned;

		curr = get_slabnode();
		if (!curr)
			break;

		if (entries++ == 0)
			*list = curr;
		else
			if (prev)
				prev->next = curr;

		assigned = sscanf(buffer, "%" STRINGIFY(SLAB_INFO_NAME_LEN)
				"s %d %d %d %d %d %d",
				curr->name, &curr->nr_active_objs,
				&curr->nr_objs, &curr->obj_size,
				&curr->nr_active_slabs, &curr->nr_slabs,
				&curr->pages_per_slab);

		if (assigned < 7) {
			fprintf(stderr, "unrecognizable data in  your slabinfo version 1.1\n\r");
			if(slab_badname_detect(buffer))
				fprintf(stderr, "Found an error in cache name at line %s\n", buffer);
			curr = NULL;
			break;
		}

		if (curr->obj_size < stats->min_obj_size)
			stats->min_obj_size = curr->obj_size;
		if (curr->obj_size > stats->max_obj_size)
			stats->max_obj_size = curr->obj_size;

		curr->cache_size = (unsigned long)curr->nr_slabs * curr->pages_per_slab * page_size;

		if (curr->nr_objs) {
			curr->use = 100 * (float)curr->nr_active_objs / curr->nr_objs;
			stats->nr_active_caches++;
		} else
			curr->use = 0;

		if (curr->obj_size)
			curr->objs_per_slab = curr->pages_per_slab *
					page_size / curr->obj_size;

		stats->nr_objs += curr->nr_objs;
		stats->nr_active_objs += curr->nr_active_objs;
		stats->total_size += (unsigned long)curr->nr_objs * curr->obj_size;
		stats->active_size += (unsigned long)curr->nr_active_objs * curr->obj_size;
		stats->nr_pages += curr->nr_slabs * curr->pages_per_slab;
		stats->nr_slabs += curr->nr_slabs;
		stats->nr_active_slabs += curr->nr_active_slabs;

		prev = curr;
	}

	if (!curr) {
		fprintf(stderr, "\rerror reading slabinfo!\n");
		return 1;
	}

	curr->next = NULL;
	stats->nr_caches = entries;
	if (stats->nr_objs)
		stats->avg_obj_size = stats->total_size / stats->nr_objs;

	return 0;
}

/*
 * parse_slabinfo10 - actual parsing routine for slabinfo 1.0 (2.2 kernels)
 *
 * Not yet implemented.  Please feel free.
 */
static int parse_slabinfo10(struct slab_info **list, struct slab_stat *stats,
				FILE *f)
{
	(void) list, (void) stats, (void) f;
	fprintf(stderr, "slabinfo version 1.0 not yet supported\n");
	return 1;
}

/*
 * slabinfo - parse the system's slabinfo and fill out both a linked list of
 * slab_info structures and the slab_stat structure
 *
 * The function returns zero on success, in which case 'list' and 'stats' are
 * valid.  Nonzero is returned on failure and the state of 'list' and 'stats'
 * are undefined.
 */
int get_slabinfo(struct slab_info **list, struct slab_stat *stats)
{
	FILE *slabfile;
	char buffer[SLABINFO_VER_LEN];
	int major, minor, ret = 0;

	slabfile = fopen(SLABINFO_FILE, "r");
	if (!slabfile) {
		perror("fopen " SLABINFO_FILE);
		return 1;
	}

	if (!fgets(buffer, SLABINFO_VER_LEN, slabfile)) {
		fprintf(stderr, "cannot read from slabinfo\n");
		fclose(slabfile);
		return 1;
	}

	if (sscanf(buffer, "slabinfo - version: %d.%d", &major, &minor) != 2) {
		fprintf(stderr, "not the good old slabinfo we know\n");
		fclose(slabfile);
		return 1;
	}

	if (major == 2)
		ret = parse_slabinfo20(list, stats, slabfile);
	else if (major == 1 && minor == 1)
		ret = parse_slabinfo11(list, stats, slabfile);
	else if (major == 1 && minor == 0)
		ret = parse_slabinfo10(list, stats, slabfile);
	else {
		fprintf(stderr, "unrecognizable slabinfo version\n");
		fclose(slabfile);
		return 1;
	}

	fclose(slabfile);

	return ret;
}
