/*----------------- superblock.c ---------------------------*
 *
 * Utility to operate on the superblock of a v0.9 raid array
 * (and maybe Version 1.0)
 *
 *	v 0.1  20/05/2020
 *----------------------------------------------------------*/

#define _LARGEFILE64_SOURCE
#include <sys/types.h>
#include <unistd.h>
#include <stdlib.h>
#include <fcntl.h>
#include <stdio.h>
#include <string.h>
#include <sys/mount.h>

#define MD_RESERVED_BYTES      (64 * 1024)
#define MD_RESERVED_SECTORS    (MD_RESERVED_BYTES / 512)

#define MD_NEW_SIZE_SECTORS(x) ((x & ~(MD_RESERVED_SECTORS - 1)) - MD_RESERVED_SECTORS)

#define MODE_SWAP  1
#define MODE_ZERO  2
#define MODE_CHECK 3
#define MODE_SAVE  4
#define MODE_RESTORE 5

int load_block( int fd, off64_t offset );

char super[4096];

int main(int argc, char *argv[])
{
    int fd, dd, i, mode;
	unsigned int magic;
    unsigned long size;
    off64_t offset;
    char *device, *file;

	mode = 0;
	device = argv[1];
	file = "";
	
	/* Find out what we are to do */
	if (argc == 3) {
		if( !strcmp( argv[2],"zero") )  mode = MODE_ZERO;
		if( !strcmp( argv[2],"swap") )  mode = MODE_SWAP;
		if( !strcmp( argv[2],"check") ) mode = MODE_CHECK;
	} else {
		if( argc == 4 ) {
			file = argv[3];
			if( !strcmp( argv[2],"save") )  mode = MODE_SAVE;
			if( !strcmp( argv[2],"restore") )  mode = MODE_RESTORE;
		}
	}
    if( !mode ) {
        fprintf(stderr, "Usage: superblock device [swap|zero|check]\nor  superblock device [save|restore] file\n");
        exit(1);
    }

	/* Check we can open the device */

	if( (mode == MODE_CHECK) || (mode == MODE_SAVE) )
		dd = open(device, O_RDONLY);
	else
		dd = open (device, O_RDWR );
	
    if (dd<0) {
        fprintf( stderr, "Can't open device <%s>\n", device );
        exit(1);
    }

	/* Check we can open any given file */
	if( *file ) {
		if( mode == MODE_SAVE )
			fd = open( file, O_CREAT | O_TRUNC | O_RDWR, 0644 );
		else
			fd = open( file, O_RDONLY );
		if( fd<0 ) {
			fprintf( stderr, "Can't open file <%s>\n", file );
			perror(file);
			exit( 1);
		}
	}

	/* Locate the Superblock and read it in */
	if (ioctl(dd, BLKGETSIZE, &size)) {
        perror("BLKGETSIZE");
        exit(1);
    }

    offset = (off64_t)MD_NEW_SIZE_SECTORS(size) * (off64_t)512;
	load_block( dd, offset);

	/* Version 1.0 Superblocks are elsewhere */

	magic = *(unsigned int *)&super[0];
	if( (magic != 0xa92b4efc) && (magic != 0xfc4e2ba9) ) {
		printf ("Stepping on %llX to ", offset);
		offset += (65536-8192);
		printf( "%llX\n", offset );
		load_block( dd, offset );
	}

	/* Check the magic number */

	magic = *(unsigned int *)&super[0];
	if( mode == MODE_CHECK ) {
		printf( "Device size is %lu sectors\n", size );
		printf( "Found: %02X %02X %02X %02X at offset %llX\n",
			super[0]&0xFF, super[1]&0xFF, super[2]&0xFF, super[3]&0xFF, offset );
		switch(magic) {
			case 0xa92b4efc:
				printf( "Little endian magic number\n" );
				exit(0);
			case 0xfc4e2ba9:
				printf( "Big endian magic number\n" );
				exit(1);
			default:
				printf( "Unrecognised superblock magic number (%08X)\n", magic );
				exit(1);
		}
		exit(0);
	}
 
	if( magic != 0xa92b4efc && magic != 0xFC4E2BA9) {
			printf( "Wrong Magic Number (%08X). Nothing done\n", magic );
			exit(1);
	}

	/* Do zero/swap/save/restore */

	switch( mode ) {
		case MODE_SWAP:
			for (i=0; i < 4096 ; i+=4) {
				char t = super[i];
				super[i] = super[i+3];
				super[i+3] = t;
				t=super[i+1];
				super[i+1]=super[i+2];
				super[i+2]=t;
			}
			/* swap the u64 events counters */
			for (i=0; i<4; i++) {
				/* events_hi and events_lo */
				char t=super[32*4+7*4 +i];
				super[32*4+7*4 +i] = super[32*4+8*4 +i];
				super[32*4+8*4 +i] = t;

				/* cp_events_hi and cp_events_lo */
				t=super[32*4+9*4 +i];
				super[32*4+9*4 +i] = super[32*4+10*4 +i];
				super[32*4+10*4 +i] = t;
			}
			break;

		case MODE_ZERO:
			memset( super, 0, sizeof(super) );
			break;

		case MODE_SAVE:
			i = write( fd, super, sizeof(super) );
			if( i != sizeof(super) ) {
				fprintf( stderr, "Error saving copy of superblock to %s\n", file );
				exit(1);
			}
			close(fd);
			break;
		
		case MODE_RESTORE:
			i = read( fd, super, sizeof(super) );
			if( i != sizeof(super) ) {
				fprintf( stderr, "Error reading 4K copy of superblock from %s\n", file );
				exit(1);
			}
			close(fd);
			break;
	}

	/* Write out new superblock data unless MODE_SAVE */

	if( mode == MODE_SAVE ) {
		printf( "Saved copy of superblock to <%s>\n", file );
		close(fd);
		exit(0);
	}
	if (lseek64(dd, offset, 0) < 0LL) {
        perror("lseek64");
        exit(1);
    }
    if (write(dd, super, 4096) != 4096) {
        perror("write");
        exit(1);
    }
	if (mode == MODE_SWAP )
		printf( "Superblock values swapped on %s\n", argv[1] );
	else
		printf( "Superblock zeroed on %s\n", argv[1] );
    exit(0);
}

	/* Load the Superblock */
int load_block( int dd, off64_t offset )
{
    if (lseek64(dd, offset, 0) < 0LL) {
	    perror("lseek64");
		exit(1);
	}
	if (read(dd, super, 4096) != 4096) {
        perror("read");
        exit(1);
    }
	return(0);
}
