/*============================================================
**
**	SPLITTER
**
**	Copyright (c)1994 Stuart Coates & Mark Matts
**
**============================================================
**
**	Portable ANSI C version.
**
**
**	Successfully compiled without change on:
**
**		Atari ST - Lattice C 5.60
**		Atari ST - GNU C 2.4.5
**		MSDOS 6.20 - GNU C (32 Bit)
**		OS/2 2.1 - GNU C
**		Sequent Dynix/PTX 2.10
**		IBM RS/6000 AIX 3.2.0
**
**
**	Porting issues:
**
**		Source code is ANSI therefore K&R compilers
**		will require the function definitions to be
**		modified to comply with K&R style.
**
**		Compiler settings should be modified so that:
**
**			short int = 16 bits
**			long int  = 32 bits
**
**
**============================================================
**
**	Version 2.00
**
**	94/02/19 SINC	Initial release.
**
**
**
*/

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/file.h>
#include <sys/types.h>
#include <sys/stat.h>

#define SPLITTER_VERSION	"2.00"
#define	BUFFER_SIZE		102400
#define EXIT_OK			0
#define EXIT_INVALID_ARGS	1
#define	EXIT_INVALID_INFILE	2
#define EXIT_INVALID_INLENGTH	3
#define	EXIT_MEMORY_ALLOC_ERR	4
#define EXIT_FILE_CREATE	5
#define EXIT_INVALID_CHECK	6
#define EXIT_FILE_READ		7
#define EXIT_INFO_READ		8
#define EXIT_FILE_WRITE		9
#define EXIT_TOO_MANY_FILES	10

/* ANSI Prototypes */
int main(int argc, char **argv);
void logo_show(void);
void usage_show(void);
int unsplit_files(char *input_file_prefix,short write_flag);
int split_files(char *input_file,char *output_files_prefix,long max_filesize);
void input_file_error(char *input_file);
void input_file_zero_error(char *input_file);
void memory_allocation_error(void);
void make_filename(char *buffer,char *prefix,short number);
long calc_check_value(unsigned long start_value,char *buffer,long length);
void file_write_error(char *file);
void null_term_line(char *string);
void validation_failed(void);
void too_many_files(void);
char *check_value_padout(unsigned long check_value, char *check_value_string);

int main(int argc, char **argv)
{
	logo_show();
	if(argc!=4 && argc!=3)	{
		usage_show();
		return(EXIT_INVALID_ARGS);
		}
	if(argc==3 && strcmp(argv[1],"-u")!=0 && strcmp(argv[1],"-c")!=0)	{
		usage_show();
		return(EXIT_INVALID_ARGS);
		}
	if(strcmp(argv[1],"-u")==0)
		return(unsplit_files(argv[2],1));
	else
	if(strcmp(argv[1],"-c")==0)
		return(unsplit_files(argv[2],0));
	else
		return(split_files(argv[1],argv[2],strtol(argv[3],NULL,10)));
}

int unsplit_files(char *input_file_prefix,short write_flag)
{
	FILE *fpin;
	FILE *fpout;
	FILE *fpinfo;
	char *buffer;
	char *info_filename;
	char *input_filename;
	short block_number=1;
	char *output_filename;
	char *workspace;
	long expand_filesize;
	long block_size;
	unsigned long stored_check_value;
	unsigned long file_check_value;
	long bytes_read;
	long error;
	char check_value_string[9];
	
	info_filename=malloc(strlen(input_file_prefix)+(5*sizeof(char)));
	if(info_filename==NULL)	{
		memory_allocation_error();
		return(EXIT_MEMORY_ALLOC_ERR);
		}
	output_filename=malloc(1024);
	if(output_filename==NULL)	{
		memory_allocation_error();
		return(EXIT_MEMORY_ALLOC_ERR);
		}
	workspace=malloc(1024);
	if(workspace==NULL)	{
		memory_allocation_error();
		return(EXIT_MEMORY_ALLOC_ERR);
		}
	input_filename=malloc(strlen(input_file_prefix)+(5*sizeof(char)));
	if(input_filename==NULL)	{
		memory_allocation_error();
		return(EXIT_MEMORY_ALLOC_ERR);
		}
	buffer=malloc(BUFFER_SIZE);
	if(buffer==NULL)	{
		memory_allocation_error();
		return(EXIT_MEMORY_ALLOC_ERR);
		}
	make_filename(info_filename,input_file_prefix,0);
	fpinfo=fopen(info_filename,"r");
	if(fpinfo==NULL)	{
		input_file_error(info_filename);
		return(EXIT_INFO_READ);
		}
	fgets(workspace,1024,fpinfo);
	null_term_line(workspace);
	printf("Files created using: %s\n",workspace);
	fgets(workspace,1024,fpinfo);
	null_term_line(workspace);
	strcpy(output_filename,workspace);
	if(write_flag==1)
		printf("Recreating file    : %s",output_filename);
	else
		printf("Original filename  : %s",output_filename);
	fgets(workspace,1024,fpinfo);
	null_term_line(workspace);
	expand_filesize=strtol(workspace,NULL,10);
	printf(" %ld Bytes.\n",expand_filesize);
	
	if(write_flag==1)	{
		fpout=fopen(output_filename,"wb");
		if(fpout==NULL)	{
			file_write_error(output_filename);
			return(EXIT_FILE_CREATE);
			}
		}
	for(;;)	{
		fgets(workspace,1024,fpinfo);
		if(feof(fpinfo))
			break;
		null_term_line(workspace);
		block_size=strtol(workspace,NULL,10);
		fgets(workspace,1024,fpinfo);
		null_term_line(workspace);
		stored_check_value=strtoul(workspace,NULL,16);
		make_filename(input_filename,input_file_prefix,block_number);
		fpin=fopen(input_filename,"rb");
		if(fpin==NULL)	{
			input_file_error(input_filename);
			return(EXIT_INFO_READ);
			}
		bytes_read=0;
		file_check_value=0;
		printf("\nReading file: %s  Check:%s",input_filename,check_value_padout(stored_check_value,check_value_string));
		for(;;)	{
			bytes_read=fread(buffer,1,BUFFER_SIZE,fpin);
			if(write_flag==1)	{
				error=fwrite(buffer,1,bytes_read,fpout);
				if(error!=bytes_read)	{
					file_write_error(output_filename);
					return(EXIT_FILE_WRITE);
					}
				}
			file_check_value=calc_check_value(file_check_value,buffer,bytes_read);
			if(feof(fpin))
				break;
			}
		fclose(fpin);
		printf("/%s",check_value_padout(file_check_value,check_value_string));
		if(file_check_value != stored_check_value)	{
			validation_failed();
			return(EXIT_INVALID_CHECK);
			}
		else	{
			printf(" - OK");
			}
		block_number++;
		}
	fclose(fpinfo);
	if(write_flag==1)
		fclose(fpout);
	printf("\n\nFinished.\n\n");
	free(buffer);
	free(workspace);
	free(info_filename);
	free(input_filename);
	free(output_filename);
	return(0);
}


int split_files(char *input_file,char *output_files_prefix,long max_filesize)
{
	FILE *fpin;
	FILE *fpout;
	FILE *fpinfo;
	struct stat fileinfo;
	char *output_filename;
	char *info_filename;
	char *buffer;
	long input_bytes_left;
	long bytes_in_current_block=0;
	long bytes_read=0;
	short block_number=1;
	unsigned long file_check_value;
	long error;
	char check_value_string[9];

	if(stat(input_file,&fileinfo)!=0)	{
		input_file_error(input_file);
		return(EXIT_INVALID_INFILE);
		}
	if(fileinfo.st_size==0)	{
		input_file_zero_error(input_file);
		return(EXIT_INVALID_INLENGTH);
		}
	input_bytes_left=fileinfo.st_size;
	
	
	if((input_bytes_left / max_filesize) > 999)	{
		too_many_files();
		return(EXIT_TOO_MANY_FILES);
		}
	
	
	output_filename=malloc(strlen(output_files_prefix)+(5*sizeof(char)));
	if(output_filename==NULL)	{
		memory_allocation_error();
		return(EXIT_MEMORY_ALLOC_ERR);
		}
	info_filename=malloc(strlen(output_files_prefix)+(5*sizeof(char)));
	if(info_filename==NULL)	{
		memory_allocation_error();
		return(EXIT_MEMORY_ALLOC_ERR);
		}
	buffer=malloc(BUFFER_SIZE);
	if(buffer==NULL)	{
		memory_allocation_error();
		return(EXIT_MEMORY_ALLOC_ERR);
		}
	make_filename(info_filename,output_files_prefix,0);
	fpin=fopen(input_file,"rb");
	if(fpin==NULL)	{
		input_file_error(input_file);
		return(EXIT_INVALID_INFILE);
		}
	fpinfo=fopen(info_filename,"w");
	if(fpinfo==NULL)	{
		file_write_error(info_filename);
		return(EXIT_FILE_CREATE);
		}
	fprintf(fpinfo,"SPLITTER "SPLITTER_VERSION"\n%s\n%ld\n",input_file,fileinfo.st_size);
	printf("Splitting file: %s into ",input_file);
	if((max_filesize*(input_bytes_left/max_filesize)) == input_bytes_left)
		printf("%d",(int)(input_bytes_left/max_filesize));
	else
		printf("%d",(int)((input_bytes_left/max_filesize)+1));
	printf(" parts.\n");
	for(;;)	{
		make_filename(output_filename,output_files_prefix,block_number);
		fpout=fopen(output_filename,"wb");
		if(fpout==NULL)	{
			file_write_error(output_filename);
			return(EXIT_FILE_CREATE);
			}
		bytes_in_current_block=0;
		file_check_value=0;
		printf("\nCreating file: %s",output_filename);
		for(;;)	{
			if(bytes_in_current_block+BUFFER_SIZE > max_filesize)	{
				bytes_read=fread(buffer,1,max_filesize-bytes_in_current_block,fpin);
				}
			else	{
				bytes_read=fread(buffer,1,BUFFER_SIZE,fpin);
				}
			file_check_value=calc_check_value(file_check_value,buffer,bytes_read);
			error=fwrite(buffer,1,bytes_read,fpout);
			if(error!=bytes_read)	{
				file_write_error(output_filename);
				return(EXIT_FILE_WRITE);
				}
			bytes_in_current_block+=bytes_read;
			input_bytes_left-=bytes_read;
			if(bytes_in_current_block==max_filesize || feof(fpin))
				break;
			}
		fclose(fpout);
		printf(" Check:%s - %ld Bytes.",check_value_padout(file_check_value,check_value_string),bytes_in_current_block);
		fprintf(fpinfo,"%ld\n%lX\n",bytes_in_current_block,file_check_value);
		block_number++;
		file_check_value=0;
		if(feof(fpin) || input_bytes_left==0)
			break;
		}
	fclose(fpin);
	fclose(fpinfo);
	printf("\n\nFinished.\n\n");
	free(buffer);
	free(info_filename);
	free(output_filename);
}

void make_filename(char *buffer,char *prefix,short number)
{
	strcpy(buffer,prefix);
	if(number<10)
		sprintf(&buffer[strlen(buffer)],".00%d",number);
	else if (number<100)
		sprintf(&buffer[strlen(buffer)],".0%d",number);
	else
		sprintf(&buffer[strlen(buffer)],".%d",number);
}

void memory_allocation_error(void)
{
	printf("\nError allocating memory block.\n");
}

void file_write_error(char *file)
{
	printf("\nError writing to file: %s\n",file);
}

void input_file_error(char *input_file)
{
	printf("\nError opening input file: %s\n",input_file);
}

void input_file_zero_error(char *input_file)
{
	printf("\nError, input file: %s is of zero length.\n",input_file);
}

void validation_failed(void)
{
	printf("\nError: File has failed validation check.\n");
}

void too_many_files(void)
{
	printf("\nError: You cannot create more than 999 files.\n");
}

void logo_show(void)
{
	printf("SPLITTER Version "SPLITTER_VERSION"\n");
	printf("This version compiled: "__DATE__" "__TIME__"\n");
	printf("Copyright (c)1994 Stuart Coates & Mark Matts\n\n");
}

void usage_show(void)
{
	printf("Usage: SPLITTER <input_file> <output_files_prefix> <max_filesize>\n");
	printf("   or: SPLITTER -u|c <split_file_prefix>\n");
}

long calc_check_value(unsigned long start_value,char *buffer,long length)
{
	register unsigned long value;
	register long count=0;
	char *ptr;
	
	ptr=buffer;
	value=start_value;
	for(count=0;count<length;count++)	{
		value=value+((long)*ptr++^(long)543);
		}
	return(value);
}

void null_term_line(char *string)
{
	char *ptr;
	
	ptr=string;
	for(;;)	{
		if(*ptr=='\r' || *ptr=='\n' || *ptr=='\0')
			break;
		ptr++;
		}
	*ptr='\0';
}

char *check_value_padout(unsigned long check_value, char *check_value_string)
{
	char tmp[10];
	
	memset(check_value_string,(int)'0',8);
	sprintf(tmp,"%lX",check_value);
	sprintf(&check_value_string[8-strlen(tmp)],"%lX",check_value);
	return(check_value_string);
}
