/****************************************************************************\
*                                                                            *
*  raidrec v0.6 - Manually try to put a RAID5 array back together.           *
*                                                                            *
*  I wrote this program when, after a few disk failures, md refused to put   *
*  my damaged RAID array back together itself, or tried to resync it incor-  *
*  rectly, further damaging my partitions.                                   *
*                                                                            *
*  Instead of figuring out how to do this, I decided to just write a small   *
*  program that does all the required XOR-ing.                               *
*                                                                            *
*  Note that I wrote this tool to scratch *my* specific itch, it's possible  *
*  that your RAID5 array was built differently. Mine had a stripe size of    *
*  64K. If yours is different, try changing the #define down here. Also, my  *
*  array is left-symmetric so if yours isn't, this tool is going to expect   *
*  the checksum at the wrong place. I haven't made this configurable. But    *
*  since AFAIK left-symmetric is the default setup, you may be lucky. It     *
*  may also work with some hardware RAID disks, depending on the setup.      *
*                                                                            *
*  Usage is very simple: Run the tool, pass it the partitions/images you're  *
*  trying to recover, and the reconstructed image will be written to std-    *
*  out. If one of the images is missing or corrupted, pass an empty string   *
*  instead. Its data will then be reconstructed.                             *
*                                                                            *
*  Example:                                                                  *
*  root@txt:/mnt/toscadisks# /root/raidrec sda2 '' sdc2 > md2                *
*                                                                            *
*  If you're impatient and want some kind of progress indicator, you can     *
*  pipe stuff through pv: http://www.ivarch.com/programs/pv.shtml            *
*                                                                            *
*  Use this tool at your own risk. I can't guarantee that it will work, but  *
*  definitely did for me. Obviously, don't use it on your partitions direc-  *
*  tly, but on block-by-block copies of them. If you have any questions,     *
*  feel free to e-mail me, but I can't guarantee a quick response, sorry.    *
*  If the program actually helped you, I'd also love to hear about it!       *
*                                                                            *
*  But please, no questions like "how do I compile this" or anything like    *
*  that. If you have to ask that, you'll be better off sending your disks    *
*  to a professional recovery company as soon as possible. Doing something   *
*  wrong here could only get you in more trouble.                            *
*                                                                            *
*  Good luck!                                                                *
*                                                                            *
*  Copyright 2009 Wilmer van der Gaast. <wilmer@gaast.net>                   *
*  Licensed under the GPLv2.                                                 *
*                                                                            *
\****************************************************************************/

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

#define MAXCOLS 5
#define STRIPESIZE 0x10000

char *getblocks( int ncols, char **istr, int xor_col );

int main( int argc, char *argv[] )
{
	int ifd[MAXCOLS];
	char **istr;
	int i, n, ncols;
	
	ncols = argc - 1;
	if( ncols > MAXCOLS || ncols < 3 )
	{
		fprintf( stderr, "Invalid number of columns.\n" );
		return 1;
	}
	
	n = 0;
	istr = malloc( sizeof( char* ) * ncols );
	for( i = 0; i < ncols; i ++ )
	{
		if( strcmp( argv[i+1], "" ) != 0 )
		{
			ifd[i] = open( argv[i+1], O_RDONLY );
			if( ifd[i] == -1 )
			{
				perror( "open" );
				return 1;
			}
			istr[i] = malloc( STRIPESIZE );
		}
		else
		{
			if( ( n ++ ) > 0 )
			{
				fprintf( stderr, "More than one missing image.\n" );
				return 1;
			}
			
			ifd[i] = -1;
			istr[i] = NULL;
		}
	}
	
	while( 1 )
	{
		static int xor_col = 0;
		
		xor_col = ( xor_col + ncols - 1 ) % ncols;
		
		for( i = 0; i < ncols; i ++ )
		{
			if( istr[i] == NULL )
				continue;
			
			if( ( n = read( ifd[i], istr[i], STRIPESIZE ) ) != STRIPESIZE )
			{
				if( n == 0 )
				{
					return 0;
				}
				else
				{
					perror( "read" );
					return 1;
				}
			}
		}
		write( 1, getblocks( ncols, istr, xor_col ), ( ncols - 1 ) * STRIPESIZE );
	}
}

char *getblocks( int ncols, char **istr, int xor_col )
{
	static char ret[(MAXCOLS-1)*STRIPESIZE], *s;
	int cur_col;
	int i;
	
	cur_col = ( xor_col + 1 ) % ncols;
	s = ret;
	
	for( i = 0; i < ( ncols - 1 ); i ++ )
	{
		if( istr[cur_col] )
		{
			memcpy( s, istr[cur_col], STRIPESIZE );
		}
		else
		{
			int j;
			
			memset( s, 0, STRIPESIZE );
			
			for( j = 0; j < ( ncols - 1 ); j ++ )
			{
				long *ip, *op;
				
				cur_col = ( cur_col + 1 ) % ncols;
				ip = (long*) istr[cur_col];
				op = (long*) s; 
				
				while( op < (long*) ( s + STRIPESIZE ) )
				{
					*op ^= *ip;
					ip ++;
					op ++;
				}
			}
			cur_col = ( cur_col + 1 ) % ncols;
		}
		s += STRIPESIZE;
		cur_col = ( cur_col + 1 ) % ncols;
	}
	
	return ret;
}

