/*
 * mkswap.c - set up a linux swap device
 *
 * (C) 1991 Linus Torvalds. This file may be redistributed as per
 * the Linux copyright.
 *
 * (C) 1999 Red Hat Software - Modified by Matt Wilson 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] [-vN] [-f] device [size-in-blocks]
 *
 *	-c   for readability checking. (Use it unless you are SURE!)
 *	-vN  for swap areas version N. (Only N=0,1 known today.)
 *      -f   for forcing swap creation even if it would smash partition table.
 *
 * The device may be a block device or an 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.
 *
 * Version 1 swap area code (for kernel 2.1.117), aeb, 981010.
 *
 * Sparc fixes, jj@ultra.linux.cz (Jakub Jelinek), 981201 - mangled by aeb.
 */

#include <stdio.h>
#include <unistd.h>
#include <string.h>
#include <fcntl.h>
#include <stdlib.h>
#include <sys/ioctl.h>		/* for _IO */
#include <sys/utsname.h>
#include <sys/stat.h>
#include <asm/page.h>		/* for PAGE_SIZE and PAGE_SHIFT */
#include <errno.h>
#include <sys/swap.h>

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

#ifndef _IO
/* pre-1.3.45 */
#define BLKGETSIZE 0x1260
#else
/* same on i386, m68k, arm; different on alpha, mips, sparc, ppc */
#define BLKGETSIZE _IO(0x12,96)
#endif

int canEnableSwap = 1;

static char * program_name = "mkswap";
static int DEV = -1;
static long PAGES = 0;
static int check = 0;
static int badpages = 0;
static int version = -1;

#ifdef __sparc__
static int sparc64 = 0;
#endif

#define MAKE_VERSION(p,q,r)	(65536*(p) + 256*(q) + (r))

static int
linux_version_code(void) {
	struct utsname my_utsname;
	int p, q, r;

	if (uname(&my_utsname) == 0) {
#ifdef __sparc__	
		if (!strcmp(my_utsname.machine, "sparc64"))
			sparc64 = 1;
#endif
		p = atoi(strtok(my_utsname.release, "."));
		q = atoi(strtok(NULL, "."));
		r = atoi(strtok(NULL, "."));
		return MAKE_VERSION(p,q,r);
	}
	return 0;
}

/*
 * The definition of the union swap_header uses the constant PAGE_SIZE.
 * Unfortunately, on some architectures this depends on the hardware model,
 * and can only be found at run time -- we use getpagesize().
 */

static int pagesize;
static int *signature_page;

struct swap_header_v1 {
        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];
} *p;

static void
init_signature_page() {
	pagesize = getpagesize();

#ifdef PAGE_SIZE
	if (pagesize != PAGE_SIZE)
		logMessage( "Assuming pages of size %d\n", pagesize);
#endif
	signature_page = (int *) malloc(pagesize);
	memset(signature_page,0,pagesize);
	p = (struct swap_header_v1 *) signature_page;
}

static void
write_signature(char *sig) {
	char *sp = (char *) signature_page;

	strncpy(sp+pagesize-10, sig, 10);
}

/* Maximum allowable number of pages in one swap.
   From 2.2.0 onwards, this depends on how many offset bits
   the architectures can actually store into the page tables
   and on 32bit architectures it is limited to 2GB at the
   same time.
   Old swap format keeps the limit of 8*pagesize*(pagesize - 10) */
#define V0_MAX_PAGES		(8 * (pagesize - 10))
#define V1_OLD_MAX_PAGES	((0x7fffffff / pagesize) - 1)
#if defined(__alpha__)
#define V1_MAX_PAGES		((1 << 24) - 1)
#elif defined(__arm__)
#define V1_MAX_PAGES		V1_OLD_MAX_PAGES /* ((1 << 23) - 1) */
#elif defined(__i386__)
#define V1_MAX_PAGES		V1_OLD_MAX_PAGES /* ((1 << 24) - 1) */
#elif defined(__mc68000__)
#define V1_MAX_PAGES		V1_OLD_MAX_PAGES /* ((1 << 20) - 1) */
#elif defined(__mips__)
#define V1_MAX_PAGES		((1 << 17) - 1)
#elif defined(__powerpc__)
#define V1_MAX_PAGES		V1_OLD_MAX_PAGES /* ((1 << 24) - 1) */
#elif defined(__sparc_v9__)
#define V1_MAX_PAGES		((3 << 29) - 1)  /* ((1L << 43) - 1) */
#elif defined(__sparc__)
#define V1_MAX_PAGES		(sparc64 ? ((3 << 29) - 1) : ((1 << 18) - 1))
#else
#define V1_MAX_PAGES		V1_OLD_MAX_PAGES
#endif

#define MAX_BADPAGES	((pagesize-1024-128*sizeof(int)-10)/sizeof(int))

static void bit_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;
}

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;
}

void fatal_error(const char * fmt_string)
{
	logMessage(fmt_string, program_name);
}

#define usage() return INST_ERROR
#define die(str) fatal_error("%s: " str "\n");  return INST_ERROR

void
page_ok(int page) {
	if (version==0)
		bit_set(signature_page, page);
}

int
page_bad(int page) {
	if (version == 0)
		bit_test_and_clear(signature_page, page);
	else {
	        if (badpages == MAX_BADPAGES) {
			die("too many bad pages");
	        }
		p->badpages[badpages] = page;
	}
	badpages++;

	return 0;
}

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

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

		sprintf(buf, _("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, buf),
				 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();
	}
	buffer = malloc(pagesize);
	if (!buffer) {
		die("Out of memory");
	}
	current_page = 0;
	while (current_page < PAGES) {
		if (!check) {
			page_ok(current_page++);
			continue;
		}

		newtScaleSet(scale, current_page);
		newtRefresh();
		
		if (do_seek && lseek(DEV,current_page*pagesize,SEEK_SET) !=
		    current_page*pagesize) {
			die("seek failed in check_blocks");
		}
		if ((do_seek = (pagesize != read(DEV, buffer, pagesize)))) {
			if ((rc = page_bad(current_page++))) return rc;
			continue;
		}
		page_ok(current_page++);
	}
	if (badpages)
		logMessage("%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
find_size (int fd)
{
	unsigned int high, low;

	low = 0;
	for (high = 1; high > 0 && 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;
	}
	return (low + 1);
}

/* return size in pages, to avoid integer overflow */
static int
get_size(const char  *file)
{
	int	fd;
	int	size;

	fd = open(file, O_RDONLY);
	if (fd < 0) {
		logMessage("open %s: %s", file, strerror(errno));
		return -1;
	}
	if (ioctl(fd, BLKGETSIZE, &size) >= 0) {
		int sectors_per_page = pagesize/512;
		size /= sectors_per_page;
	} else {
		size = find_size(fd) / pagesize;
	}
	close(fd);
	return size;
}

int enableswap(char * devicename, int size, int checkBlocks) {
	struct stat statbuf;
	int maxpages;
	int goodpages;
	int offset;
	char device_name[100];
	int version_code = linux_version_code();
	
	check = checkBlocks;

	if (testing) {
	    return 0;
	}

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

	init_signature_page();	/* get pagesize */

	if (!device_name) {
		logMessage(
			"%s: error: Nowhere to set up swap on?\n",
			program_name);
		usage();
	}
	if (!PAGES) {
		if ((PAGES = get_size(device_name)) < 0)
		    return INST_ERROR;
	}

	if (version == -1) {
		if (PAGES <= V0_MAX_PAGES)
			version = 0;
		else if (version_code < MAKE_VERSION(2,1,117))
			version = 0;
		else if (pagesize < 2048)
			version = 0;
		else
			version = 1;
	}
	if (version != 0 && version != 1) {
		logMessage( "%s: error: unknown version %d\n",
			program_name, version);
		usage();
	}
	if (PAGES < 10) {
		logMessage(
			"%s: error: swap area needs to be at least %ldkB\n",
			program_name, (long)(10 * pagesize / 1024));
		usage();
	}
	DEV = open(device_name,O_RDWR);
	if (DEV < 0 || fstat(DEV, &statbuf) < 0) {
		logMessage("open %s: %s", device_name, strerror(errno));
	}
	if (!S_ISBLK(statbuf.st_mode))
		check=0;
	else if (statbuf.st_rdev == 0x0300 || statbuf.st_rdev == 0x0340) {
		die("Will not try to make swapdevice");
	}
#ifdef __sparc__
	if (version == 0) {
		unsigned char *buffer = (unsigned char *)signature_page;
		unsigned short *q, sum;

		if (read(DEV, buffer, 512) != 512) {
			die("fatal: first page unreadable");
		}
		if (buffer[508] == 0xDA && buffer[509] == 0xBE) {
			q = (unsigned short *)(buffer + 510);
			for (sum = 0; q >= (unsigned short *) buffer;)
				sum ^= *q--;
			if (!sum) {
				if (version_code >= MAKE_VERSION(2,1,117)) {
					logMessage( "\
Device '%s' contains a valid Sun disklabel.\n\
This probably means creating v0 swap would destroy your partition table.\n\
Using v1 swap format, which is compatible with 2.1.117 and later kernels.\n",
						device_name);
					version = 1;
				} else {
					logMessage( "\
Device '%s' contains a valid Sun disklabel.\n\
This probably means creating v0 swap would destroy your partition table\n\
No swap created. If you really want to create swap v0 on that device, use\n\
the -f option of mkswap to force it.\n", device_name);
					return INST_ERROR;
				}
			}
		}
	}
#endif

	if (!version)
		maxpages = V0_MAX_PAGES;
	else if (version_code >= MAKE_VERSION(2,2,1))
		maxpages = V1_MAX_PAGES;
	else {
		maxpages = V1_OLD_MAX_PAGES;
		if (maxpages > V1_MAX_PAGES)
			maxpages = V1_MAX_PAGES;
	}
	if (PAGES > maxpages) {
		PAGES = maxpages;
		logMessage( "%s: warning: truncating swap area to %ldkB\n",
			program_name, PAGES * pagesize / 1024);
	}

	if (version == 0 || check)
	    if (check_blocks(signature_page, device_name)) {
		close(DEV);
		unlink(device_name);
		newtPopWindow();
		return INST_ERROR;
	    }
	
	if (version == 0 && !bit_test_and_clear(signature_page,0)) {
		die("fatal: first page unreadable");
	}
	if (version == 1) {
		p->version = version;
		p->last_page = PAGES-1;
		p->nr_badpages = badpages;
	}

	goodpages = PAGES - badpages - 1;
	if (goodpages <= 0) {
		die("Unable to set up swap-space: unreadable");
	}
	logMessage("Setting up swapspace on %s version %d, size = %ld bytes\n",
		device_name, version, (long)(goodpages*pagesize));
	write_signature((version == 0) ? "SWAP-SPACE" : "SWAPSPACE2");

	offset = ((version == 0) ? 0 : 1024);
	if (lseek(DEV, offset, SEEK_SET) != offset) {
		die("unable to rewind swap-device");
	}
	if (write(DEV,(char*)signature_page+offset, pagesize-offset)
	    != pagesize-offset) {
		die("unable to write signature page");
	}
	/*
	 * A subsequent swapon() will fail if the signature
	 * is not actually on disk. (This is a kernel bug.)
	 */
	if (fsync(DEV)) {
		 die("fsync failed");
	}

	close(DEV);

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

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

	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 = NULL;
    char buf[80];
    int i, j, rc, row;
    struct fstabEntry entry;
    struct fstabEntry ** entries = NULL;
    struct fstab fstab;
    static int chkBadBlocks = 0;
    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 (!forceFormat) {
	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             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/%-11s  %9d", table->parts[i].device, 
		    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 (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");

		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;
}
