/*
 * 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 <sys/swap.h>
#include <unistd.h>

#include <asm/page.h>

#define BLKGETSIZE 0x1260

#include "devices.h"
#include "install.h"
#include "intl.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;

int canEnableSwap = 1;

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;
	newtGrid grid;

	if (check) {
		form = newtForm(NULL, NULL, 0);

		sprintf(buffer, _("Formatting swap space on device %s..."), 
			file);
		scale = newtScale(-1, -1, 58, PAGES);

		grid = newtCreateGrid(1, 2);
		newtGridSetField(grid, 0, 0, NEWT_GRID_COMPONENT,
			         newtLabel(-1, -1, buffer),
				 0, 0, 0, 0, NEWT_ANCHOR_LEFT, 0);
		newtGridSetField(grid, 0, 1, NEWT_GRID_COMPONENT, scale,
				 0, 1, 0, 0, 0, 0);
		newtGridAddComponentsToForm(grid, form, 1);

		newtGridWrappedWindow(grid, _("Formatting"));
		
		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) {
	    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, 0)) {
		logMessage("mkswap: swapon() failed: %s\n", strerror(errno));
	}

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

	return 0;
}

int activeSwapSpace(struct partitionTable * table, struct fstab * finalFstab) {
    newtComponent form, checkList, okay, cancel, sb, text, answer, label;
    newtComponent check, checkbox, blank;
    newtGrid grid, subgrid, buttons, checkgrid;
    char * states;
    char buf[80];
    int i, j, rc, row;
    struct fstabEntry entry;
    struct fstabEntry ** entries;
    struct fstab fstab;
    static int chkBadBlocks = 1;
    char doCheck = ' ';

    if (!canEnableSwap) return INST_NOP;

    fstab = copyFstab(finalFstab);    

    form = newtForm(NULL, NULL, 0);

    for (i = j = 0; i < table->count; i++)
	if (table->parts[i].type == PART_SWAP)
	    j++;

    if (!j) {
	rc = newtWinChoice(_("No Swap Space"), _("Repartition"), _("Continue"),
			   _("You don't have any swap space defined. Would "
			    "you like to continue, or repartition your disk?"));
	if (rc != 2)
	   return INST_CANCEL;
	
	return 0;
    }

    if (!kickstart) {
	if (j > 3) {
	    sb = newtVerticalScrollbar(47, 7, 3, 9, 10);
	} else
	    sb = NULL;

	checkList = newtForm(sb, NULL, 0);
	if (sb) newtFormSetHeight(checkList, j > 3);

	text = newtTextboxReflowed(-1, -1, _("What partitions would you like "
			   "to use for swap space? This will destroy any "
			   "information already on the partition."),
			    52, 0, 15, 0);

	label = newtLabel(-1, -1, 
			"    Device          Begin        End   Size (k)");

	states = alloca(sizeof(char) * table->count);
	entries = alloca(sizeof(*entries) * table->count);

	for (i = 0, row = 0; i < table->count; i++) {
	    if (table->parts[i].type != PART_SWAP) continue;

	    for (j = 0; j < fstab.numEntries; j++) 
		if (!strcmp(table->parts[i].device, fstab.entries[j].device))
		    break;

	    if ((j < fstab.numEntries && fstab.entries[j].mntpoint) || 
		!testing)
		states[i] = '*';
	    else
		states[i] = ' ';

	    if (j < fstab.numEntries) 
		entries[i] = fstab.entries + j;
	    else
		entries[i] = NULL;

	    sprintf(buf, "/dev/%-5s  %9d  %9d  %9d", table->parts[i].device, 
		    table->parts[i].begin, table->parts[i].end, 
		    table->parts[i].size);
	    check = newtCheckbox(-1, row++, buf, states[i], NULL, 
				     &states[i]);
	    newtFormAddComponent(checkList, check);
	}

	if (row > 3) {
	    blank = newtForm(NULL, NULL, 0);
	    newtFormSetWidth(blank, 2);
	    newtFormSetHeight(blank, 3);
	    newtFormSetBackground(blank, NEWT_COLORSET_CHECKBOX);
	    checkgrid = newtGridHCloseStacked(
				NEWT_GRID_COMPONENT, checkList,
				NEWT_GRID_COMPONENT, blank,
				NEWT_GRID_COMPONENT, sb, NULL);
	} else {
	    checkgrid = newtGridHCloseStacked(NEWT_GRID_COMPONENT, checkList,
					      NULL);
	}

	checkbox = newtCheckbox(-1, -1, _("Check for bad blocks during format"),
				    chkBadBlocks ? '*' : ' ', NULL, &doCheck);

	buttons = newtButtonBar(_("Ok"), &okay, _("Back"), &cancel, NULL);
	subgrid = newtCreateGrid(1, 3);
	newtGridSetField(subgrid, 0, 0, NEWT_GRID_COMPONENT, label,
			 0, 0, 0, 0, NEWT_ANCHOR_LEFT, 0);
	newtGridSetField(subgrid, 0, 1, NEWT_GRID_SUBGRID, checkgrid,
			 0, 0, 0, 0, NEWT_ANCHOR_LEFT, 0);
	newtGridSetField(subgrid, 0, 2, NEWT_GRID_COMPONENT, checkbox,
			 0, 1, 0, 0, NEWT_ANCHOR_LEFT, 0);
 	grid = newtGridBasicWindow(text, subgrid, buttons);
	newtGridAddComponentsToForm(grid, form, 1);
	newtGridWrappedWindow(grid, _("Active Swap Space"));
	newtGridFree(grid, 1);

	answer = newtRunForm(form);

	newtFormDestroy(form);
	newtPopWindow();

	chkBadBlocks = (doCheck != ' ');

	if (answer == cancel) {
	    freeFstab(fstab);
	    return INST_CANCEL;
	}
    }

    for (i = 0; i < table->count; i++) {
	if (table->parts[i].type != PART_SWAP) continue;

        if (kickstart || states[i] != ' ') {
	    if (!kickstart && entries[i])
		entries[i]->mntpoint = strdup("swap");
	    else {
		initFstabEntry(&entry);
		entry.device = strdup(table->parts[i].device);
		entry.size = table->parts[i].size;
		entry.type = table->parts[i].type;
		entry.tagName = table->parts[i].tagName;
		entry.mntpoint = strdup("swap");

		addFstabEntry(&fstab, entry);
	    }
	} else if (entries[i]) {
	    free(entries[i]->mntpoint);
	    entries[i]->mntpoint = NULL;
	}
    }

    if (canEnableSwap) {
	for (i = 0; i < fstab.numEntries; i++) {
	    if (fstab.entries[i].type == PART_SWAP &&
		fstab.entries[i].mntpoint) {
		enableswap(fstab.entries[i].device, 0, chkBadBlocks);
		canEnableSwap = 0;
	    }
	}
    }

    freeFstab(*finalFstab);
    *finalFstab = copyFstab(&fstab);
    freeFstab(fstab);

    return 0;
}
