/*
 * mkswap.c - set up a linux swap device
 *
 * (C) 1991 Linus Torvalds. This file may be redistributed as per
 * the Linux copyright.
 *
 * (C) 1996 Red Hat Software - Modified by Erik Troan for Red Hat Software 
 *     still GPLed, of course
 */

/*
 * 20.12.91  -	time began. Got VM working yesterday by doing this by hand.
 *
 * Usuage: mkswap [-c] device [size-in-blocks]
 *
 *	-c for readablility checking (use it unless you are SURE!)
 *
 * The device may be a block device or a image of one, but this isn't
 * enforced (but it's not much fun on a character device :-).
 *
 * Patches from jaggy@purplet.demon.co.uk (Mike Jagdis) to make the
 * size-in-blocks parameter optional added Wed Feb  8 10:33:43 1995.
 */

#include <errno.h>
#include <fcntl.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/ioctl.h>
#include <sys/stat.h>
#include <unistd.h>

#include <linux/mm.h>

#include "devices.h"
#include "install.h"
#include "log.h"
#include "mkswap.h"
#include "newt.h"
#include "windows.h"

#ifndef __linux__
# define volatile
#endif

#define TEST_BUFFER_PAGES 8

static int DEV = -1;
static long PAGES = 0;
static int check = 0;
static int badpages = 0;

static long bit_test_and_set (unsigned int *addr, unsigned int nr)
{
	unsigned int r, m;

	addr += nr / (8 * sizeof(int));
	r = *addr;
	m = 1 << (nr & (8 * sizeof(int) - 1));
	*addr = r | m;
	return (r & m) != 0;
}

static int bit_test_and_clear (unsigned int *addr, unsigned int nr)
{
	unsigned int r, m;

	addr += nr / (8 * sizeof(int));
	r = *addr;
	m = 1 << (nr & (8 * sizeof(int) - 1));
	*addr = r & ~m;
	return (r & m) != 0;
}

static int check_blocks(int * signature_page, char * file)
{
	unsigned int current_page;
	int do_seek = 1;
	static char buffer[PAGE_SIZE];
	newtComponent form = NULL, scale = NULL;

	if (check) {
		newtOpenWindow(10, 10, 60, 5, "Formatting");
		
		form = newtForm(NULL, NULL, 0);

		sprintf(buffer, "Formatting swap space on device %s...", file);
		newtFormAddComponent(form, newtLabel(1, 1, buffer));
		scale = newtScale(1, 3, 58, PAGES);
		newtFormAddComponent(form, scale);
		newtDrawForm(form);
		newtRefresh();
	}

	current_page = 0;
	while (current_page < PAGES) {
		if (!check) {
			bit_test_and_set(signature_page,current_page++);
			continue;
		}

		newtScaleSet(scale, current_page);
		newtRefresh();

		if (do_seek && lseek(DEV,current_page*PAGE_SIZE,SEEK_SET) !=
		current_page*PAGE_SIZE) {
			logMessage("mkswap: seek failed in check_blocks");
			return INST_ERROR;
		}
		if ((do_seek = (PAGE_SIZE != read(DEV, buffer, PAGE_SIZE)))) {
			bit_test_and_clear(signature_page,current_page++);
			badpages++;
			continue;
		}
		bit_test_and_set(signature_page,current_page++);
	}
	if (badpages)
		logMessage("\t%d bad page%s\n",badpages,(badpages>1)?"s":"");

	if (check) {
		newtPopWindow();
		newtFormDestroy(form);
	}

	return 0;
}

static long valid_offset (int fd, int offset)
{
	char ch;

	if (lseek (fd, offset, 0) < 0)
		return 0;
	if (read (fd, &ch, 1) < 1)
		return 0;
	return 1;
}

static int count_blocks (int fd)
{
	int high, low;

	low = 0;
	for (high = 1; valid_offset (fd, high); high *= 2)
		low = high;
	while (low < high - 1)
	{
		const int mid = (low + high) / 2;

		if (valid_offset (fd, mid))
			low = mid;
		else
			high = mid;
	}
	valid_offset (fd, 0);
	return (low + 1);
}

static int get_size(const char  *file, long int * sizeptr)
{
	int	fd;
	int	size;

	fd = open(file, O_RDWR);
	if (fd < 0) {
		logMessage("mkswap: failed to get size of device %s", file);
		return INST_ERROR;
	}
	if (ioctl(fd, BLKGETSIZE, &size) >= 0) {
		close(fd);
		*sizeptr = size * 512;
		return 0;
	}
		
	*sizeptr = count_blocks(fd);
	close(fd);

	return 0;;
}

int enableswap(char * device_name, int size, int checkBlocks) {
	struct stat statbuf;
	int goodpages;
	int signature_page[PAGE_SIZE/sizeof(int)];
	char devicefile[100];

	check = checkBlocks;

	if (testing) {
	    messageWindow("Testing", "I would make and enable swap on %s",
			  device_name);
	    return 0;
	}

	memset(signature_page,0,PAGE_SIZE);

	if (*device_name == '/') {
	    strcpy(devicefile, device_name);
	} else {
	    sprintf(devicefile, "/tmp/%s", device_name);
	    if (devMakeInode(device_name, devicefile)) {
		return INST_ERROR;
	    }
	}

	if (size)
		PAGES = size;
	else {
		if (get_size(devicefile, &PAGES)) {
		    unlink(devicefile);
		    return INST_ERROR;
		}

		PAGES = PAGES / PAGE_SIZE;
	}

	if (PAGES<10) {
		logMessage("mkswap: error: swap area needs to be at least "
				"%ldkB", 10 * PAGE_SIZE / 1024);
		unlink(devicefile);
		return INST_ERROR;
	}

	if (PAGES > 8 * (PAGE_SIZE - 10)) {
	        PAGES = 8 * (PAGE_SIZE - 10);
		logMessage("mkswap: warning: truncating %s swap to %ldkB",
			device_name, PAGES * PAGE_SIZE / 1024);
	}

	DEV = open(devicefile,O_RDWR);
	if (DEV < 0 || fstat(DEV, &statbuf) < 0) {
		logMessage("mkswap: failed to open device: %s\n", device_name);
		unlink(devicefile);
		return INST_ERROR;
	}
	if (!S_ISBLK(statbuf.st_mode))
		check=0;
	else if (statbuf.st_rdev == 0x0300 || statbuf.st_rdev == 0x0340) {
		logMessage("mkswap: will not try to make swapdevice on '%s'");
		close(DEV);
		unlink(devicefile);
		return INST_ERROR;
	}

	if (check_blocks(signature_page, device_name)) {
		close(DEV);
		unlink(devicefile);
		newtPopWindow();
		return INST_ERROR;
	}

	if (!bit_test_and_clear(signature_page,0)) {
		logMessage("mkswap: first page unreadable");
		close(DEV);
		unlink(devicefile);
		newtPopWindow();
		return INST_ERROR;
	}

	goodpages = PAGES - badpages - 1;
	if (goodpages <= 0) {
		logMessage("mkswap: unable to set up swap-space: unreadable");
		close(DEV);
		unlink(devicefile);
		newtPopWindow();
		return INST_ERROR;
	}

	logMessage("setting up swapspace, device = %s, size = %d bytes",
		device_name, goodpages*PAGE_SIZE);
	strncpy((char*)signature_page+PAGE_SIZE-10,"SWAP-SPACE",10);
	if (lseek(DEV, 0, SEEK_SET)) {
		logMessage("mkswap: unable to rewind swap-device");
		close(DEV);
		unlink(devicefile);
		newtPopWindow();
		return INST_ERROR;
	}

	if (PAGE_SIZE != write(DEV, signature_page, PAGE_SIZE)) {
		logMessage("mkswap: unable to write signature page");
		close(DEV);
		unlink(devicefile);
		newtPopWindow();
		return INST_ERROR;
	}

	close(DEV);

	if (swapon(devicefile)) {
		logMessage("mkswap: swapon() failed: %s\n", strerror(errno));
	}

	if (*device_name != '/') 
	    unlink(devicefile);

	return 0;
}