/*
 * 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
 *
 * (C) 1997,1998 Jakub Jelinek - Modified for sparc64 (GPL as well :))
 *
 * (C) 1998 Daniel Quinlan
 */

/*
 * 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.
 * Changes from quinlan@transmeta.com (Daniel Quinlan) to support
 * large swap files on Tue Feb 24 02:52:48 1998.
 */

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

#include <asm/page.h>

/* This was hard coded, and incorrect on Sparc for
 * example, tsk tsk... -DaveM
 */
#define BLKGETSIZE _IO(0x12,96)

#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

#ifndef __sparc__
#define PAGE_SIZE_BUF PAGE_SIZE
#else
#define PAGE_SIZE_BUF 8192
static int page_size = PAGE_SIZE;
#undef PAGE_SIZE
#define PAGE_SIZE page_size

/* swap_header length is PAGE_SIZE bytes */
union swap_header {
	int	signature_page[PAGE_SIZE_BUF / sizeof(int)];
#ifdef __sparc__
	struct magic32 {
		char reserved[4096 - 10];
		char magic[10];
	} magic32;
	struct magic64 {
		char reserved[8192 - 10];
		char magic[10];
	} magic64;
#else
	struct magic {
		char reserved[PAGE_SIZE - 10];
		char magic[10];
	} magic;
#endif
	struct info {
		char bootbits[1024];    /* Space for disklabel etc. */
		unsigned int version;
		unsigned int last_page;
		unsigned int nr_badpages;
		unsigned int padding[125];
		unsigned int badpages[1];
	} info;
};

static void check_page_size(void)
{
       struct utsname uts;

       if (uname (&uts) >= 0 && !strcmp (uts.machine, "sparc64"))
               page_size = 8192;
}
#endif

#define TEST_BUFFER_PAGES 8
#define MAGIC_STRING "SWAPSPACE2"
#define SWAP_VERSION 1

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(union swap_header *first_page, char * file, int version)
{
	unsigned int current_page;
	int do_seek = 1;
	static char buffer[PAGE_SIZE_BUF];
	newtComponent form = NULL, scale = NULL;
	newtGrid grid;

	if (version && !check)
		return 0;
	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 (!version && !check) {
			bit_test_and_set(first_page->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)))) {
			if (!current_page) {
				logMessage("mkswap: first block unreadable");
				return INST_ERROR;
			}
			if (!version)
				bit_test_and_clear(first_page->signature_page,current_page++);
			else {
				if ((void *) &first_page->info.badpages[badpages+1] >
				    (void *) (((char *)first_page) + PAGE_SIZE - 10)) {
					logMessage("mkswap: too many bad blocks");
					return INST_ERROR;
				}
				first_page->info.badpages[badpages++] = current_page++;
			}
			badpages++;
			continue;
		}
		if (!version)
			bit_test_and_set(first_page->signature_page,current_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, int version) {
	struct stat statbuf;
	int goodpages;
	char devicefile[100];
	union swap_header first_page;

	check = checkBlocks;
	badpages = 0;
	
	if (version < 0) {
	/* FIXME: This is for upgrade: should find out the starting cylinder
	   on sparc and ask the user whether he wants old or new swap type. */
		version = 1;
	}
	
	#ifdef __sparc__
	    check_page_size();
	#endif

	if (testing) {
	    return 0;
	}

	memset(&first_page,0,PAGE_SIZE_BUF);

	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 (version) {
		logMessage("mkswap: swap %s will be incompatible with kernels prior to 2.1.110", device_name);
	}

	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) && !version) {
	        PAGES = 8 * (PAGE_SIZE - 10);
		logMessage("mkswap: warning: truncating %s swap to %ldkB",
			device_name, PAGES * PAGE_SIZE / 1024);
	} else if (PAGES > (0x7FFFFFFFL / PAGE_SIZE)) {
		PAGES = (0x7FFFFFFFL / PAGE_SIZE);
		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\n", device_name);
		close(DEV);
		unlink(devicefile);
		return INST_ERROR;
	}

	/* check for bad blocks */
	if (check_blocks(&first_page, device_name, version)) {
		close(DEV);
		unlink(devicefile);
		newtPopWindow();
		return INST_ERROR;
	}

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

	if (goodpages >= 0xffffffff / PAGE_SIZE)
		logMessage("setting up swapspace, device = %s, size = %d Kbytes",
			   device_name, goodpages*(PAGE_SIZE/1024));
	else
		logMessage("setting up swapspace, device = %s, size = %d bytes",
			   device_name, goodpages*PAGE_SIZE);
			   
	if (!version)
		strncpy((char*)(first_page.signature_page)+PAGE_SIZE-10,"SWAP-SPACE",10);
	else {
		/* set up header */
		if (lseek(DEV, 0, SEEK_SET)) {
			logMessage("mkswap: unable to rewind swap-device");
			close(DEV);
			unlink(devicefile);
			newtPopWindow();
			return INST_ERROR;
		}
		if (1024 != read(DEV, &first_page, 1024)) {
			logMessage("mkswap: unable to read swap header");
			close(DEV);
			unlink(devicefile);
			newtPopWindow();
			return INST_ERROR;
		}
		strncpy((char*)(first_page.signature_page)+PAGE_SIZE-10, MAGIC_STRING, 10);
		first_page.info.version = SWAP_VERSION;
		first_page.info.last_page = PAGES;
		first_page.info.nr_badpages = badpages;
		/* HACK to workaround a bug in 2.1.1xx - 2.1.126?? */
		if (!badpages) {
			first_page.info.nr_badpages = 1;
			first_page.info.badpages[0] = 1;
		}
	}
	
	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, first_page.signature_page, PAGE_SIZE)) {
		logMessage("mkswap: unable to write header 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,
		    int forceFormat) {
    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, newformat = 0;
    int cnt, cyl0cnt;
    int maxswap;
    struct fstabEntry entry;
    struct fstabEntry ** entries;
    struct fstab fstab;
    static int chkBadBlocks = 0;
    char doCheck = ' ';
    char *types, *fstypes;

    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++;
	    
    types = (char *)alloca(sizeof(char) * table->count);
    fstypes = (char *)alloca(sizeof(char) * (table->count + fstab.numEntries));
    memset (types, 0, sizeof(char) * table->count);
    memset (fstypes, 0, sizeof(char) * (table->count + fstab.numEntries));

    if (!j) {
#ifdef __sparc__    
no_swaps_left:
#endif
	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 defined(__alpha__)
    maxswap = (8192-10)*8*8;
#elif defined(__sparc__)
    {
	struct utsname u;
	if (!uname(&u) && !strcmp(u.machine, "sparc64"))
	    maxswap = (8192-10)*8*8;
	else
	    maxswap = (4096-10)*8*4;
    }
#else
    maxswap = (4096-10)*8*4;
#endif

    cnt = 0; cyl0cnt = 0;
    for (i = 0; i < table->count; i++) {
	if (table->parts[i].type != PART_SWAP) continue;
	cnt++;
#ifdef __sparc__
	if (!table->parts[i].begin) {
	    newformat |= 2;
	    types[i] = 2;
	    cyl0cnt++;
	}
#endif
	if (table->parts[i].size > maxswap) {
	    newformat |= 1;
	    types[i] = 1;
	}
    }
    
    if (!forceFormat) {
    
	if (newformat == 1) {
	    if (newtWinChoice(_("Swap Size"), _("Yes"), _("No"),
		_("Some of your swap partitions are too large "
		  "for 2.0 and early 2.1 kernels. The maximum size "
		  "of a swap partition for them is %d Megabytes.\n"
		  "Would you like to create swap space covering your "
		  "whole swap partitions, but make it incompatible with "
		  "older kernels?"), maxswap / 1024) == 2)
		newformat = 0;
	}
#ifdef __sparc__
	if (newformat >= 2) {
	    if (newformat == 3) {
		if (newtWinChoice(_("Swap Size"), _("Yes"), _("No"),
		    _("Some of your swap partitions are too large "
		      "for 2.0 and early 2.1 kernels, some are located "
		      "at cylinder 0 of your disks (the 2.0 and early 2.1 "
		      "swap format cannot handle that). The maximum size "
		      "of a swap partition for them is %d Megabytes.\n"
		      "Would you like to create swap space covering your "
		      "whole swap partitions, but make it incompatible with "
		      "older kernels? If you say No, partitions "
		      "starting at cylinder 0 cannot be used for swaps."), maxswap / 1024) == 2)
		    newformat = 0;
	    } else {
		if (newtWinChoice(_("Swap Size"), _("Yes"), _("No"),
	    	    _("Some of your swap partitions start at cylinder 0, "
	    	      "which makes it impossible to create swap space compatible "
	    	      "with kernels 2.0 and early 2.1. Would you like to create "
	    	      "the swap spaces there and make the swaps incompatible with "
	    	      "older kernels?")) == 2)
		    newformat = 0;
	    }
	    if (!newformat && cnt == cyl0cnt)
		goto no_swaps_left;
	}
#endif
	if (!newformat)
	    memset(types, 0, sizeof(char) * table->count);

	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. Partitions "
			   "marked with 2.1+ will be incompatible with kernels "
			   "earlier than 2.1.110."),
			    58, 0, 16, 0);

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

	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;
		fstypes[j] = types[i];
	    } else
		entries[i] = NULL;
		
	    sprintf(buf, "/dev/%-5s  %9d  %s", table->parts[i].device, 
		    table->parts[i].size, types[i] ? "2.1+" : "Old ");
	    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 (forceFormat || states[i] != ' ') {
	    if (!forceFormat && 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");

		j = addFstabEntry(&fstab, entry);
		fstypes[j] = types[i];
	    }
	} 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, fstypes[i] != 0);
		canEnableSwap = 0;
	    }
	}
    }

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

    return 0;
}
