/*
 * free.c	- show free whatever
 *
 * Copyright (c) 1992 Branko Lankester
 *
 */

#define __KERNEL__
#include <sys/types.h>
#include <sys/stat.h>
#include <sys/mman.h>
#include <stdio.h>
#include <getopt.h>
#include <stdlib.h>
#include <unistd.h>
#include <linux/genhd.h>
#include <linux/sched.h>
#include <linux/mm.h>
#include <drivers/block/blk.h>
#include "ps.h"
#include "psdata.h"

#define MAX_SWAPFILES 8

#define	SWP_USED	1
#define	SWP_WRITEOK	3

struct swap_info_struct {
	unsigned long flags;
	struct inode * swap_file;
	unsigned int swap_device;
	unsigned char * swap_map;
	unsigned char * swap_lockmap;
	int pages;
	int lowest_bit;
	int highest_bit;
	unsigned long max;
};

extern int optind;
extern char *optarg;
int nr_swapfiles;
int pg_shift = 2;
int verbose = 0;
unsigned long high_mem;

void show_memory(int total[5], int show);
void show_swap(int total[5], int show);
void show_totals(int total[5]);
void show_req(int dev);
void show_buf(int dev);
void show_ino(int dev);
void show_desc(void);

int
main(int argc, char *argv[])
{
    int opt, interv = -1, dev = 0, one = 0;
    int req = 0, mem = 0, swap = 0, tot = 0, buf = 0, ino = 0, file = 0;
    int total[5];
    char *devpath = NULL;

    if (open_psdb()) {
	perror("cannot open psdatabase");
	exit(2);
    }
    while ((opt = getopt(argc, argv, "pkcrmstbifvS:d:")) != -1) {
	switch (opt) {
	    case 'p': pg_shift = 0; break;
	    case 'k': pg_shift = 2; break;
	    case 'c': pg_shift = 12; break;
	    case 'r': req = 1; break;
	    case 'm': mem = 1; break;
	    case 's': swap = 1; break;
	    case 't': tot = 1; break;
	    case 'b': buf = 1; break;
	    case 'i': ino = 1; break;
	    case 'f': file = 1; break;
	    case 'S': interv = atoi(optarg); break;
	    case 'd': devpath = optarg; break;
	    case 'v': verbose = 1; break;
	    default:
		fprintf(stderr, "usage: free [-mstribpc] [-d block-dev] [-S interval]\n");
		exit(2);
	}
    }
    if (devpath) {
	struct stat st;
	if (stat(devpath, &st) == -1) {
	    fprintf(stderr, "cannot stat ");
	    perror(devpath);
	    exit(2);
	}
	dev = st.st_rdev;
    }
    high_mem = get_kword(k_addr("_high_memory"));
    nr_swapfiles = get_kword(k_addr("_nr_swapfiles"));

    if (!mem && !req && !swap && !buf && !ino && !file && !tot) {
	mem = 1;		/* default: mem, swap, and tot */
	swap = tot = (nr_swapfiles != 0);
    }

    while (1) {
	if (req)
	    show_req(dev);

	if (!one && (buf || ino || file))
	    printf("            total     space      used     dirty    locked    cached     fr/ch\n");

	if (buf)
	    show_buf(dev);

	if (ino)
	    show_ino(dev);

	if (file)
	    show_desc();

	if (!one && (mem || swap || tot))
	    printf("              size       used      cache       free     shared\n");

	if (mem || tot)
	    show_memory(total, mem);

	if (swap || tot)
	    show_swap(total, swap);

	if (tot)
	    show_totals(total);

	switch (interv) {
	    case 0: break;
	    case -1: exit(0);
	    default: sleep(interv);
	}
	one = ((req + (buf || ino || file) + (mem || swap || tot)) == 1);
    }

    exit(0);
}

void
show_memory(int total[5], int show)
{
    static unsigned short *memmap;
    static unsigned long pages, _mem_map, _buffermem;
    static unsigned long _nr_buffer_heads;
    unsigned used=0, freepg=0, shared=0;
    unsigned long buf_pages, buffer_heads;
    int i;

    if (memmap == NULL) {
	pages = get_kword(k_addr("_high_memory")) / 4096;
	memmap = (unsigned short *) xmalloc(pages * sizeof *memmap);
	_mem_map = get_kword(k_addr("_mem_map"));
	_buffermem = k_addr("_buffermem");
	_nr_buffer_heads = k_addr("_nr_buffer_heads");
    }
    buf_pages = get_kword(_buffermem) / 4096;
    buffer_heads = get_kword(_nr_buffer_heads);
    /* add space used by buffer heads */
    buf_pages += buffer_heads / (4096 / sizeof(struct buffer_head));
    kmemread(memmap, _mem_map, pages * sizeof *memmap);
    for (i = 0; i < pages; ++i) {
	if (memmap[i] & MAP_PAGE_RESERVED)
	    continue;
	switch (memmap[i]) {
	    case 0:	++freepg; break;
	    case 1:	++used; break;
	    default:	shared += memmap[i] - 1; ++used; break;
	}
    }

    total[0] = (freepg + used) << pg_shift;
    total[1] = (used - buf_pages) << pg_shift;
    total[2] = buf_pages << pg_shift;
    total[3] = freepg << pg_shift;
    total[4] = shared << pg_shift;

    if (show)
	printf("memory: %10d %10d %10d %10d %10d\n",
	    total[0], total[1], total[2], total[3], total[4]);
}

void
show_swap(int total[5], int show)
{
    unsigned freepg=0, used, shared;
    int i, sw, n;
    unsigned char *swapmap;
    struct swap_info_struct swapinfo[MAX_SWAPFILES];

    if (nr_swapfiles == 0) {
	printf("No swap device.\n");
	return;
    }
    kmemread(swapinfo, k_addr("_swap_info"), sizeof swapinfo);
    for (sw = 0; sw < nr_swapfiles; ++sw) {
	if (!(swapinfo[sw].flags & SWP_USED))
	    continue;
	freepg = 0; used = 0; shared = 0;
	n = swapinfo[sw].max;
	swapmap = (unsigned char *) xmalloc(n);
	kmemread(swapmap, (unsigned long) swapinfo[sw].swap_map, n);
	for (i = 0; i < n; ++i)
	    switch (swapmap[i]) {
		case 0: ++freepg; break;
		case 128: break;
		default: shared += swapmap[i] - 1;
		case 1: ++used;
	    }

	total[0] += (used + freepg) << pg_shift;
	total[1] += used << pg_shift;
	total[3] += freepg << pg_shift;
	total[4] += shared << pg_shift;

	if (show)
	    printf("swap%d:  %10d %10d            %10d %10d\n", sw,
		(used + freepg) << pg_shift,
		used << pg_shift,
		freepg << pg_shift,
		shared << pg_shift);
    }
}

void
show_totals(int total[5])
{
    printf("total:  %10d %10d %10d %10d %10d\n",
	total[0], total[1], total[2], total[3], total[4]);
}

void
show_req(int dev)
{
    struct request request[NR_REQUEST];
    int i;
    int readreq = 0, writereq = 0;
    int maxread = 0, maxwrite = 0;
    static unsigned long _request;

    if (!_request)
	_request = k_addr("_request");
    kmemread(request, _request, sizeof request);
    if (dev) {
	for (i=0; i<NR_REQUEST; ++i)
	    if (request[i].dev == dev && (request[i].nr_sectors & 1))
		break;
	if (i < NR_REQUEST) {
	    while (1) {
		printf("%5ld%c  ", request[i].sector, request[i].cmd ? 'w' : 'r');
		if (!request[i].next)
		    break;
		i = ((int) request[i].next - _request) / sizeof(struct request);
	    }
	    printf(" --\n");
	}
	return;
    }
    for (i=0; i<NR_REQUEST; ++i) {
	if (dev && request[i].dev != dev)
		continue;
	if (request[i].dev != -1) {
	    if (request[i].cmd) {
		++writereq;
		if (request[i].nr_sectors > maxwrite)
		    maxwrite = request[i].nr_sectors;
	    } else {
		++readreq;
		if (request[i].nr_sectors > maxread)
		    maxread = request[i].nr_sectors;
	    }
	}
    }
    if (readreq + writereq > 0)
	printf("blk requests:  read: %d (max = %d)  write: %d (max = %d)  free: %d\n",
	    readreq, maxread, writereq, maxwrite,
	    NR_REQUEST - readreq - writereq);
}

void
show_buf(int dev)
{
    int nr_buf;
    struct buffer_head *bh;
    int in_use = 0, dirt = 0, locked = 0, cached = 0;
    int chained = 0, buffers, size = 0;
    unsigned long first, tmp;

    mmap_init();
    buffers = nr_buf = KWORD(k_addr("_nr_buffers"));
    first = KWORD(k_addr("_free_list"));

    for (tmp = first; --buffers >= 0; tmp = (unsigned long) bh->b_next_free) {
	if (!tmp || tmp > high_mem) {
	    fprintf(stderr, "Bad pointer in free list.\n");
	    return;
	}
	bh = KPTR(tmp);
	if (mmap_page(tmp) < 0 && verbose)
		printf("mmap_page failed\n");
	size += bh->b_size;
	if (dev && bh->b_dev != dev)
		continue;
	if (bh->b_count) ++in_use;
	if (bh->b_dirt) ++dirt;
	if (bh->b_lock) ++locked;
	if (bh->b_dev) ++cached;
	if (bh->b_reqnext) ++chained;
	if (verbose)
	    printf("%04x %5ld  %c%c%c%c %3d\n", bh->b_dev, bh->b_blocknr,
		bh->b_count ? 'U' : ' ',
		bh->b_dirt ? 'D' : ' ',
		bh->b_lock ? 'L' : ' ',
		bh->b_reqnext ? 'C' : ' ',
		bh->b_count);
	if ((unsigned long) bh->b_next_free == first && buffers) {
	    printf ("Warning: %d buffers are not counted in following totals\n",
		    buffers);
	    break;
	}
    }
    printf("buffers:%9d %9d %9d %9d %9d %9d %9d\n",
	nr_buf, size / 1024, in_use, dirt, locked, cached, chained);
}

void
show_ino(int dev)
{
    struct inode *ino;
    int in_use, dirt, locked;
    int inodes, nr_inodes;
    unsigned long first, tmp;
    int retry_cnt = 6;

    mmap_init();
retry:
    in_use = dirt = locked = 0;
    inodes = nr_inodes = KWORD(k_addr("_nr_inodes"));
    first = tmp = KWORD(k_addr("_first_inode"));

    while (--inodes >= 0) {
	if (!tmp || tmp > high_mem) {
	    if (verbose)
		fprintf(stderr, "show_ino: bad pointer: %#lx\n", tmp);
	    break;
	}
	if (mmap_page(tmp) < 0) {
	    printf("mmap_page failed\n");
	    return;
	}
	ino = KPTR(tmp);
	if (verbose > 1) {
	    printf("%04x %6ld %4d\n", ino->i_dev, ino->i_ino, ino->i_count);
	}
	if (dev && ino->i_dev != dev)
	    continue;
	if (ino->i_count) ++in_use;
	if (ino->i_dirt) ++dirt;
	if (ino->i_lock) ++locked;
	tmp = (unsigned long) ino->i_next;
	if (tmp == first)
	    break;

    }
    if (inodes) {
	if (verbose)
	    fprintf(stderr, "show_ino: list changed, inodes = %d\n", inodes);
	if (--retry_cnt)
	    goto retry;
    }
    if (!retry_cnt) {
	fprintf(stderr, "couldn't get inode list\n");
	if (!verbose)
	    return;	/* if verbose: print results anyway */
    }
    printf("inodes: %9d           %9d %9d %9d           %9ld\n",
	nr_inodes, in_use, dirt, locked,
	get_kword(k_addr("_nr_free_inodes")));
    return;
}

void
show_desc(void)
{
    struct file *file;
    int in_use;
    int files, nr_files;
    unsigned long first, tmp;
    int retry_cnt = 6;

    mmap_init();
retry:
    in_use = 0;
    files = nr_files = KWORD(k_addr("_nr_files"));
    first = tmp = KWORD(k_addr("_first_file"));

    while (--files >= 0) {
	if (!tmp || tmp > high_mem) {
	    if (verbose)
		fprintf(stderr, "show_desc: bad pointer: %#lx\n", tmp);
	    break;
	}
	if (mmap_page(tmp) < 0) {
	    printf("mmap_page failed\n");
	    return;
	}
	file = KPTR(tmp);
	if (file->f_count) ++in_use;
	tmp = (unsigned long) file->f_next;
	if (tmp == first)
	    break;

    }
    if (files) {
	if (verbose)
	    fprintf(stderr, "show_desc: list changed, files = %d\n", files);
	if (--retry_cnt)
	    goto retry;
    }
    if (!retry_cnt) {
	fprintf(stderr, "couldn't get files list\n");
	if (!verbose)
	    return;	/* if verbose: print results anyway */
    }
    printf("descr:  %9d           %9d\n", nr_files, in_use);
    return;
}
