/*************************************************************************
**
**	PROGRAM:		JRD file split utility program
**	MODULE:			spit.cpp
**	DESCRIPTION:	Command line interpreter for backup file split/join
**					utility program
**
**
**************************************************************************
 * The contents of this file are subject to the Interbase Public
 * License Version 1.0 (the "License"); you may not use this file
 * except in compliance with the License. You may obtain a copy
 * of the License at http://www.Inprise.com/IPL.html
 *
 * Software distributed under the License is distributed on an
 * "AS IS" basis, WITHOUT WARRANTY OF ANY KIND, either express
 * or implied. See the License for the specific language governing
 * rights and limitations under the License.
 *
 * The Original Code was created by Inprise Corporation
 * and its predecessors. Portions created by Inprise Corporation are
 * Copyright (C) Inprise Corporation.
 *
 * All Rights Reserved.
 * Contributor(s): ______________________________________.
*/

#include "firebird.h"
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>
#include "../jrd/common.h"
#include "../jrd/gdsassert.h"
#include <stdarg.h>

#include <fcntl.h>
#include <ctype.h>
#if TIME_WITH_SYS_TIME
# include <sys/time.h>
# include <time.h>
#else
# if HAVE_SYS_TIME_H
#  include <sys/time.h>
# else
#  include <time.h>
# endif
#endif
#include "../burp/spit.h"
#include "../burp/burpswi.h"

#ifdef HAVE_UNISTD_H
#include <unistd.h>
#endif

#ifdef WIN_NT
#include <io.h> // usage of non-ANSI open/read/write/close functions
#endif

static const int mode_read	= O_RDONLY;
static const int mode_write	= O_WRONLY | O_CREAT;
static const int mask		= 0666;

// Definitions for GSPLIT 
enum gsplit_option
{
	IN_SW_SPIT_0	= 0,   // the unknowable switch
	IN_SW_SPIT_SP	= 30,  // splits back up files
	IN_SW_SPIT_JT	= 31  // joins back up files
};


static in_sw_tab_t spit_in_sw_table[] =
{
	{IN_SW_SPIT_SP,	0,	"SPLIT_BK_FILE", 0, 0, 0, FALSE, 0, 0, NULL},
	{IN_SW_SPIT_JT,	0,	"JOIN_BK_FILE",	 0, 0, 0, FALSE, 0, 0, NULL},
	{IN_SW_SPIT_0,	0,	NULL,			 0, 0, 0, FALSE, 0, 0, NULL}
};

/*************************************
** backup files header record
**************************************
*/

struct header_rec {
	TEXT name[18];
	TEXT date_time[30];
	TEXT text1[11];
	TEXT num[4];
	TEXT text2[4];
	TEXT total[4];
	TEXT text3[2];
	TEXT fl_name[MAX_FILE_NM_LEN];
};

const size_t header_rec_len = sizeof(header_rec);
static const char *header_rec_name	= "InterBase/gsplit, ";

/*************************************
** backup files structure
**************************************
*/

struct b_fil {
	b_fil *b_fil_next;
	TEXT *b_fil_name;
	SLONG b_fil_number;
	double b_fil_size;
};

const size_t b_fil_len = sizeof(b_fil);


	/*****************************************************
	**
	** local function declarations
	**
	******************************************************
	*/

static int conv_ntoc(SLONG, TEXT *);
static int free_file_list(b_fil*);
static int final_flush_io_buff(UCHAR *, SLONG, FILE_DESC);
static int final_read_and_write(FILE_DESC, FILE_DESC,
								const TEXT*, SLONG, UCHAR **, bool*);
static int flush_io_buff(UCHAR *, SLONG,
						 FILE_DESC, double, SLONG *, bool*);
static int get_file_name(const SCHAR *, double, b_fil**);
static int get_file_size(const SCHAR *, const SCHAR *, double *);
static int get_function_option(const SCHAR *, gsplit_option*, const SCHAR *,
	const in_sw_tab_t* const);
static int gen_multy_bakup_files(b_fil*, FILE_DESC, SLONG);
static int set_hdr_str(TEXT *, const TEXT *, SLONG, SLONG);
static int join_multy_bakup_files(b_fil*);
static int print_clo(const TEXT *);
static int read_and_write(FILE_DESC, FILE_DESC,
						  const TEXT*, SLONG,
						  double, UCHAR **, bool*, double *, SLONG *);
static int read_and_write_for_join(FILE_DESC, const TEXT*,
								   UCHAR **, SLONG, SLONG *);
static int write_header(b_fil*, header_rec, FILE_DESC, TEXT *);



	/*****************************************************
	**
	**	M A I N              P R O G R A M
	**
	******************************************************
	*/

int main( int argc, char *argv[])
{
	SLONG ret_cd, file_num = 0;

	const SCHAR *prog_name = argv[0];

	if (argc < 2) {
		fprintf(stderr, "%s: No Command Line Option Specified\n",
				   argv[0]);
		ret_cd = print_clo(prog_name);
		return FB_FAILURE;
	}

/************************
** Fields initialization
*************************
*/

	b_fil* file_ptr = NULL;
	b_fil* file_list = NULL;
	b_fil* prev_file = NULL;
	// Strange, never met a program with tenths of bytes... may need updating
	// from double to int64.
	double file_size = -1;
	gsplit_option sw_replace = IN_SW_SPIT_0;

/*******************************
 ** Initialize in_sw_table table.
 *******************************
*/

	for (in_sw_tab_t* in_sw_tab = spit_in_sw_table; in_sw_tab->in_sw_name;
		in_sw_tab++)
	{
		in_sw_tab->in_sw_state = FALSE;
	}

	/**********************************
	** validating command line options
	***********************************
	*/
	
	bool file_nm_sw = false;
	const SCHAR* string = NULL;

	const SCHAR* const* const end = argv + argc;
	++argv;
	while (argv < end)
	{
		string = *argv;
		if (*string == '-') {
			argv++;
			ret_cd = get_function_option(prog_name, &sw_replace,
										 string, spit_in_sw_table);
			if (ret_cd == FB_FAILURE) {
				ret_cd = free_file_list(file_list);
				return FB_FAILURE;
			}
		}						// end of processing (*string == '-')
		else {					// processing function specific command line options

			switch (sw_replace) {
			case (IN_SW_SPIT_SP):
				if (!file_nm_sw) {	// process file name 
					file_size = 0;
					file_num = file_num + 1;

					if (file_num > MAX_NUM_OF_FILES) {
						fprintf(stderr,
								   "%s: maximum of files is %d\n",
								   prog_name, MAX_NUM_OF_FILES);
						ret_cd = print_clo(prog_name);
						ret_cd = free_file_list(file_list);
						return FB_FAILURE;
					}

					if (strlen(string) > MAX_FILE_NM_LEN) {
						fprintf(stderr,
								   "%s: file name %s is too long\n",
								   prog_name, string);
						fprintf(stderr,
								   "%s: maximum length of file name is %d bytes\n",
								   prog_name, MAX_FILE_NM_LEN);
						ret_cd = print_clo(prog_name);
						ret_cd = free_file_list(file_list);
						return FB_FAILURE;
					}

					ret_cd = get_file_name(string, file_size, &file_ptr);

					if (ret_cd == FB_FAILURE) {
						ret_cd = free_file_list(file_list);
						return FB_FAILURE;
					}

					file_nm_sw = true;
					file_ptr->b_fil_number = file_num;

					if (!file_list)
						file_list = prev_file = file_ptr;
					else {
						prev_file->b_fil_next = file_ptr;
						prev_file = file_ptr;
					}

				}				// processing file name
				else {			// processing file size

					file_nm_sw = false;
					ret_cd = get_file_size(prog_name, string, &file_size);
					if (ret_cd == FB_FAILURE) {
						ret_cd = free_file_list(file_list);
						return FB_FAILURE;
					}
				}				// end of processing file size specification
				file_ptr->b_fil_size = file_size;
				break;

			case (IN_SW_SPIT_JT):
				ret_cd = get_file_name(string, file_size, &file_ptr);

				if (ret_cd == FB_FAILURE) {
					ret_cd = free_file_list(file_list);
					return FB_FAILURE;
				}

				file_num = file_num + 1;
				file_ptr->b_fil_number = file_num;

				if (!file_list)
					file_list = prev_file = file_ptr;
				else {
					prev_file->b_fil_next = file_ptr;
					prev_file = file_ptr;
				}				// end of processing file size specification
				break;

			default:
				fprintf(stderr, "%s: invalid option '%s'\n",
						   prog_name, string);
				ret_cd = print_clo(prog_name);
				ret_cd = free_file_list(file_list);
				return FB_FAILURE;
				break;
			}					// end of switch (sw_replace) 

			argv++;
		}						// processing function specific command line options 
	}							// while (argv < end)

	if (!file_list && sw_replace != IN_SW_SPIT_0) {
		fprintf(stderr,
				   "%s: invalid option '%s', rest of parameters is missing\n",
				   prog_name, string);
		ret_cd = print_clo(prog_name);
		ret_cd = free_file_list(file_list);
		return FB_FAILURE;
	}

	FILE_DESC input_file_desc;
	switch (sw_replace) {
	case (IN_SW_SPIT_SP):
		input_file_desc = GBAK_STDIN_DESC;
		ret_cd = gen_multy_bakup_files(file_list, input_file_desc, file_num);
		if (ret_cd == FB_FAILURE) {
			fprintf(stderr,
					   "%s: progam fails to generate multi-volumn back-up files\n",
					   prog_name);
			ret_cd = free_file_list(file_list);
			return FB_FAILURE;
		}
		break;

	case (IN_SW_SPIT_JT):
		ret_cd = join_multy_bakup_files(file_list);
		if (ret_cd == FB_FAILURE) {
			fprintf(stderr,
					   "%s: progam fails to join multi-volumn back-up files\n",
					   prog_name);
			ret_cd = free_file_list(file_list);
			return FB_FAILURE;
		}
		break;

	default:
		break;
	}

/********************************************************
**
**	free all the storage allocated for backup files
**
*********************************************************
*/

	ret_cd = free_file_list(file_list);
	return FB_SUCCESS;

}								// end of main()


static int get_function_option(const SCHAR* prog_name,
							   gsplit_option* sw_replace,
							   const SCHAR* string,
							   const in_sw_tab_t* const in_sw_table)
{
/********************************************************************
**
**	g e t _ f u n c t i o n _ o p t i o n
**
*********************************************************************
**
**	Functional description:
**
**		processing command line option and set proper function switch
**
*********************************************************************
*/
	SLONG ret_cd;

	if (strlen(string) == 1) {
		fprintf(stderr, "%s: invalid option '%s'\n", prog_name, string);
		ret_cd = print_clo(prog_name);
		return FB_FAILURE;
	}

	//gsplit_option op_specified = *sw_replace; Commented, where is it used here???
	const SCHAR* q;
	const in_sw_tab_t* in_sw_tab;
	for (in_sw_tab = in_sw_table; q = in_sw_tab->in_sw_name; in_sw_tab++)
	{
		SCHAR c;
		for (const SCHAR* p = string + 1; c = *p++;)
			if (UPPER(c) != *q++)
				break;
		if (!c) {
			if (*sw_replace == IN_SW_SPIT_0) {
				*sw_replace = (gsplit_option) in_sw_tab->in_sw;
				return FB_SUCCESS;
			}
			else {
				if (*sw_replace != in_sw_tab->in_sw) {
					fprintf(stderr,
							   "%s: invalid option '%s', incompatible option\n",
							   prog_name, string);
					ret_cd = print_clo(prog_name);
					return FB_FAILURE;
				}
				else			// compatible option 
					break;
			}
		}						// end of if (!c)
	}							// end of for loop 

	if (!in_sw_tab->in_sw) {
		fprintf(stderr, "%s: invalid option '%s'\n", prog_name, string);
		ret_cd = print_clo(prog_name);
		return FB_FAILURE;
	}

	return FB_SUCCESS;
}


static int get_file_name( const SCHAR * string, double file_size, b_fil** file_ptr)
{
/********************************************************************
**
**	g e t _ f i l e _ n a m e
**
*********************************************************************
**
**	Functional description:
**
**		processing file name specification and returns file structure
**		pointer.
**
*********************************************************************
*/

	b_fil* const temp_ptr = (b_fil*) malloc(b_fil_len);

	*file_ptr = temp_ptr;
	temp_ptr->b_fil_name = (TEXT*) malloc(strlen(string) + 1);

	temp_ptr->b_fil_next = NULL;
	strcpy(temp_ptr->b_fil_name, string);
	temp_ptr->b_fil_number = 0;
	temp_ptr->b_fil_size = file_size;

	return FB_SUCCESS;
}


static int get_file_size(const SCHAR* prog_name, const SCHAR* string, double* file_size)
{
/********************************************************************
**
**	g e t _ f i l e _ s i z e
**
*********************************************************************
**
**	Functional description:
**
**		processing file size specification and returns calculated
**		file size.
**
*********************************************************************
*/

	SCHAR c;
	SLONG ret_cd;

	for (const SCHAR* p = string; c = *p++;)
	{
		if (c == '\0')
			break;

		if (isdigit(c))
			*file_size = *file_size * 10 + (c - '0');
		else {
			if ((isalpha(c)) &&
				(UPPER(c) == 'G' || UPPER(c) == 'K' || UPPER(c) == 'M'))
			{
				SLONG size_indicator = 0;
				switch (UPPER(c)) {
				case ('K'):
					size_indicator = K_BYTES;
					break;

				case ('M'):
					size_indicator = M_BYTES;
					break;

				case ('G'):
					size_indicator = G_BYTES;
					break;

				default:
					break;
				}				// end of switch( UPPER (c) ) 

				*file_size = *file_size * size_indicator;
			}
			else {				// invalid size indicator 

				fprintf(stderr,
						   "%s: invalid size indicator '%s'\n", prog_name,
						   string);
				ret_cd = print_clo(prog_name);
				return FB_FAILURE;
			}
		}
	}
	if (*file_size < MIN_FILE_SIZE) {	// handling user specifies file size 0 
		fprintf(stderr,
				   "%s: invalid option '%s', minimum file size is 1 megabyte\n",
				   prog_name, string);
		ret_cd = print_clo(prog_name);
		return FB_FAILURE;
	}

	return FB_SUCCESS;
}


static int gen_multy_bakup_files(b_fil* file_list,
								 FILE_DESC input_file_desc, SLONG file_num)
{
/********************************************************************
**
**	g e n _ m u l t y _ b a c k u p _ f i l e s
**
*********************************************************************
**
**	Functional description:
**
**		processing input data from stdin and splits the data into
**		multiple back-up files.
**
**		allocates an 16K bytes I/O buffer
**		intilializes header record common fields
**		do forever
**			walk through the backup file chain
**			intilializes header record unique fields
**			open backup file
**			writes out header record to backup file
**			points to the next backup file in the chain
**			calculates the actual file size ( minus header record length )
**			if the actual file size less than 16K bytes
**				set I/O size to actual file size
**			otherwise
**				set I/O size to 16K byte long
**			when it is the last backup file
**				reads data from standard input as much as indicated by I/O size
**					and writes it out to the last backup file until no EOF.
**				issues error message when disk space full condition is detected
**			otherwise reads and writes to backup files util EOF
**				if disk full cobdition is detected
**					flush the remaining data in the I/O buffer to subsequence
**							backup files
**				go back to normal read and write process util EOF
**
*********************************************************************
*/

	SLONG byte_write, ret_cd;
	TEXT header_str[header_rec_len], num_arr[5];
	header_rec hdr_rec;

// CVC: there's a can of worms here. First, this function assumes it can free
// the io_buffer's allocated memory without keeping a second copy of that pointer.
// However, io_buffer can't be declared UCHAR* const because its address is
// passed to final_read_and_write() and read_and_write() and both functions
// thus suggest, by taking a UCHAR** that they can change the pointer's address;
// but in practice they never affect it, so fixing those functions to take simply
// UCHAR* would allow the correct declaration for io_buffer to succeed.
	//UCHAR* const io_buffer = (UCHAR *) malloc(IO_BUFFER_SIZE);
	UCHAR* io_buffer = (UCHAR *) malloc(IO_BUFFER_SIZE);

	if (!io_buffer) {
		fprintf(stderr, "I/O buffer allocation failed\n");
		return FB_FAILURE;
	}

	size_t pos;
	for (pos = 0; pos < header_rec_len; pos++)
		header_str[pos] = BLANK;

	pos = 0;
	ret_cd = set_hdr_str(header_str, header_rec_name,
						 pos, sizeof(hdr_rec.name));
	size_t indx;
	for (indx = 0; indx < sizeof(hdr_rec.name); indx++)
		hdr_rec.name[indx] = BLANK;

	pos = pos + sizeof(hdr_rec.name);
	time_t clock = time(0);  // was SLONG
	ret_cd = set_hdr_str(header_str, ctime(&clock),
						 pos, sizeof(hdr_rec.date_time));
	for (indx = 0; indx < sizeof(hdr_rec.date_time); indx++)
		hdr_rec.date_time[indx] = BLANK;

	pos = pos + sizeof(hdr_rec.date_time);
	ret_cd = set_hdr_str(header_str, ", file No. ",
						 pos, sizeof(hdr_rec.text1));
	for (indx = 0; indx < sizeof(hdr_rec.text1); indx++)
		hdr_rec.text1[indx] = BLANK;

	for (indx = 0; indx < sizeof(hdr_rec.num); indx++)
		hdr_rec.num[indx] = BLANK;

	pos = pos + sizeof(hdr_rec.text1) + sizeof(hdr_rec.num);
	ret_cd = set_hdr_str(header_str, " of ", pos, sizeof(hdr_rec.text2));
	for (indx = 0; indx < sizeof(hdr_rec.text2); indx++)
		hdr_rec.text2[indx] = BLANK;

	ret_cd = conv_ntoc(file_num, num_arr);
	if (ret_cd == FB_FAILURE) {
		free(io_buffer);
		fprintf(stderr,
				   "gsplit could not convert numeric data to character data\n");
		return FB_FAILURE;
	}

	num_arr[sizeof(num_arr) - 1] = TERMINAL;
	pos = pos + sizeof(hdr_rec.text2);
	ret_cd = set_hdr_str(header_str, num_arr, pos, sizeof(hdr_rec.total));
	for (indx = 0; indx < sizeof(hdr_rec.total); indx++)
		hdr_rec.total[indx] = BLANK;

	pos = pos + sizeof(hdr_rec.total);
	ret_cd = set_hdr_str(header_str, ", ", pos, sizeof(hdr_rec.text3));
	for (indx = 0; indx < sizeof(hdr_rec.text3); indx++)
		hdr_rec.text3[indx] = BLANK;

	for (indx = 0; indx < sizeof(hdr_rec.fl_name); indx++)
		hdr_rec.fl_name[indx] = BLANK;

	FILE_DESC output_fl_desc = 0;
	bool end_of_input = false, flush_done = false;
	const TEXT* file_name = NULL;
	SLONG io_size = 0;
	b_fil* fl_ptr = file_list;

	double byte_read = 0;
	double file_size = 0;
	
	while (true) {
		if (fl_ptr != NULL) {
			byte_read = 0;
			byte_write = 0;
			if (!fl_ptr->b_fil_next && (fl_ptr->b_fil_size == 0))
			{
				fl_ptr->b_fil_size = MIN_FILE_SIZE;
			}

			file_size = fl_ptr->b_fil_size - header_rec_len;
			file_name = fl_ptr->b_fil_name;

			output_fl_desc = open(file_name, mode_write, mask);
			if (output_fl_desc == -1) {
				free(io_buffer);
				fprintf(stderr, "can not open back up file %s\n",
						   file_name);
				return FB_FAILURE;
			}

			ret_cd =
				write_header(fl_ptr, hdr_rec, output_fl_desc, header_str);
			if (ret_cd == FB_FAILURE) {
				free(io_buffer);
				fprintf(stderr,
						   "could not write header record to file %s\n",
						   file_name);
				return FB_FAILURE;
			}

			fl_ptr = fl_ptr->b_fil_next;
		}

		if (file_size < IO_BUFFER_SIZE)
			io_size = (SLONG) file_size;
		else
			io_size = IO_BUFFER_SIZE;

		if (!fl_ptr) {
			while (!end_of_input) {
				ret_cd = final_read_and_write(input_file_desc, output_fl_desc,
											  file_name, io_size, &io_buffer,
											  &end_of_input);
				if (ret_cd == FB_FAILURE) {
					free(io_buffer);
					return FB_FAILURE;
				}

				if (end_of_input) {
					free(io_buffer);
					return FB_SUCCESS;
				}
			}
		}
		else {
			while ((file_size > byte_read) && (fl_ptr != NULL)) {
				ret_cd = read_and_write(input_file_desc, output_fl_desc,
										file_name, io_size, file_size,
										&io_buffer, &end_of_input,
										&byte_read, &byte_write);
				switch (ret_cd) {
				case FB_FAILURE:
					{
						free(io_buffer);
						return FB_FAILURE;
						break;
					}

				case FILE_IS_FULL:
					{
						byte_read = 0;	/* reset byte read count,
										   ** prepare for next read 
										 */
						UCHAR* remaining_io = io_buffer + byte_write;
						SLONG remaining_io_len = IO_BUFFER_SIZE - byte_write;
						while (!flush_done && (fl_ptr != NULL)) {
							if (!fl_ptr->b_fil_next && fl_ptr->b_fil_size == 0)
								fl_ptr->b_fil_size = MIN_FILE_SIZE;

							file_size = fl_ptr->b_fil_size - header_rec_len;
							file_name = fl_ptr->b_fil_name;

							output_fl_desc =
								open(file_name, mode_write, mask);
							if (output_fl_desc == -1) {
								free(io_buffer);
								fprintf(stderr,
										   "can not open back up file %s\n",
										   file_name);
								return FB_FAILURE;
							}
							ret_cd = write_header(fl_ptr, hdr_rec,
												  output_fl_desc, header_str);
							if (ret_cd == FB_FAILURE) {
								free(io_buffer);
								fprintf(stderr,
										   "fail to write header rec to file %s\n",
										   file_name);
								return FB_FAILURE;
							}

							fl_ptr = fl_ptr->b_fil_next;
							if (!fl_ptr) {
								ret_cd = final_flush_io_buff(remaining_io,
															 remaining_io_len,
															 output_fl_desc);
								if (ret_cd == FB_FAILURE) {
									fprintf(stderr,
											   "gsplit could not do backup due");
									fprintf(stderr,
											   " to lack of space or I/O problem\n");
									free(io_buffer);
									return FB_FAILURE;
								}
							}
							else {	// got a lot of backup files 

								ret_cd = flush_io_buff(remaining_io,
													   remaining_io_len,
													   output_fl_desc,
													   file_size,
													   &byte_write,
													   &flush_done);
								if (ret_cd == FB_FAILURE) {
									fprintf(stderr,
											   "gsplit could not do backup due");
									fprintf(stderr, " I/O problem\n");
									free(io_buffer);
									return FB_FAILURE;
								}
								if (flush_done) {
									file_size = file_size - byte_write;
									byte_write = 0;
								}
								else {
									remaining_io = remaining_io + byte_write;
									remaining_io_len = remaining_io_len -
										byte_write;
								}
							}
						}		// end of while loop
						break;
					} // case FILE_IS_FULL

				default:
					break;
				}

				if (end_of_input) {
					free(io_buffer);
					return FB_SUCCESS;
				}
			}
		}
	}	// end of while ( true )
}


static int read_and_write(FILE_DESC input_file_desc,
						  FILE_DESC output_fl_desc,
						  const TEXT* file_name,
						  SLONG io_size,
						  double file_size,
						  UCHAR** io_buffer,
						  bool* end_of_input,
						  double *byte_read,
						  SLONG* byte_write)
{

/********************************************************************
**
**	r e a d _ a n d _ w r i t e
**
*********************************************************************
**
**	Functional description:
**
**		Read data from input file and write the data out to output
**		file.
**
*********************************************************************
*/

	SLONG read_cnt, last_read_size;

	/********************************************************
	**	when number of byte read + number of byte goint to
	**	be read is greater then file size, then calculate
	**	the size for the last read and do the last read for
	**	the current backup file. Otherwise read as mush data
	**	as will fit in the current backup file.
	**********************************************************
	*/

	if (*byte_read + io_size > file_size) {
		last_read_size = (SLONG) (file_size - *byte_read);
		read_cnt = read(input_file_desc, *io_buffer, last_read_size);
	}
	else
		read_cnt = read(input_file_desc, *io_buffer, io_size);

	switch (read_cnt) {
	case (0):					// no more data to be read 
		close(output_fl_desc);
		*end_of_input = true;
		*byte_read = *byte_read + read_cnt;
		return FB_SUCCESS;
		break;

	case (-1):					// read failed 
		close(output_fl_desc);
		fprintf(stderr,
				   "fail to read input from stdin, errno = %d\n", errno);
		return FB_FAILURE;
		break;

	default:					// read ok 
		*byte_read = *byte_read + read_cnt;
		break;
	}

	const SLONG write_cnt = write(output_fl_desc, *io_buffer, read_cnt);

	switch (write_cnt) {
	case (-1):					// write failed 
		close(output_fl_desc);
		return FB_FAILURE;
		break;

	default:
		if (write_cnt == read_cnt)	// write ok 
			return FB_SUCCESS;
		else {					// write less data then it reads in 

			close(output_fl_desc);
			*byte_write = write_cnt;
			return FILE_IS_FULL;
		}
		break;
	}
}


static int final_read_and_write(FILE_DESC input_file_desc,
								FILE_DESC output_fl_desc,
								const TEXT* file_name,
								SLONG io_size,
								UCHAR** io_buffer,
								bool* end_of_input)
{

/********************************************************************
**
**	f i n a l _ r e a d _ a n d _ w r i t e
**
*********************************************************************
**
**	Functional description:
**
**		Read all data from input file and write the data out to output
**		file.
**
*********************************************************************
*/

	const SLONG read_cnt = read(input_file_desc, *io_buffer, io_size);

	switch (read_cnt) {
	case (0):					// no more data to be read 
		close(output_fl_desc);
		*end_of_input = true;
		return FB_SUCCESS;
		break;

	case (-1):					// read failed 
		close(output_fl_desc);
		fprintf(stderr,
				   "problem when reading input file, errno = %d\n", errno);
		return FB_FAILURE;
		break;

	default:					// read ok 
		break;
	}

	const SLONG write_cnt = write(output_fl_desc, *io_buffer, read_cnt);

	switch (write_cnt) {
	case (-1):					// write failed 
		close(output_fl_desc);
		return FB_FAILURE;
		break;

	default:
		if (write_cnt == read_cnt)	// write ok 
			return FB_SUCCESS;
		else {					// write less data then it reads in 

			fprintf(stderr,
					   "There is no enough space to write to back up file %s\n",
					   file_name);
			close(output_fl_desc);
			return FB_FAILURE;
		}
		break;
	}
}


static int join_multy_bakup_files( b_fil* file_list)
{
/********************************************************************
**
**	j o i n _ m u l t y _ b a c k u p _ f i l e s
**
*********************************************************************
**
**	Functional description:
**
**		allocates I/O buffer and walks through backup files` chain.
**		calls read_and_write_for_join routine to read data from
**		backup file and to write data to standard output file
**		which later to be processed by gbak. Finally frees up
**		I/O buffer
**
*********************************************************************
*/

	FILE_DESC output_fl_desc = GBAK_STDOUT_DESC;

	// See comment near the beginning of gen_multy_bakup_files() as it
	// also applies to read_and_write_for_join().
	//UCHAR* const io_buffer = (UCHAR *) malloc(IO_BUFFER_SIZE);
	UCHAR* io_buffer = (UCHAR *) malloc(IO_BUFFER_SIZE);

	if (io_buffer == 0) {
		fprintf(stderr, "I/O buffer allocation failed\n");
		return FB_FAILURE;
	}

	SLONG cnt = 0, total_int = 0;
	// Why two variables to achieve this simple loop?
	b_fil* next_fl = NULL;
	for (b_fil* fl_ptr = file_list; fl_ptr; fl_ptr = next_fl) {
		cnt++;
		next_fl = fl_ptr->b_fil_next;
		const TEXT* file_name = fl_ptr->b_fil_name;

		SLONG ret_cd =
			read_and_write_for_join(output_fl_desc, file_name, &io_buffer,
									cnt, &total_int);

		if (ret_cd == FB_FAILURE) {
			free(io_buffer);
			return FB_FAILURE;
		}

	}							// end of for loop 

	free(io_buffer);
	return FB_SUCCESS;
}


static int read_and_write_for_join(FILE_DESC output_fl_desc,
								const TEXT* file_name,
								UCHAR ** io_buffer,
								SLONG cnt, 
								SLONG * total_int)
{
/********************************************************************
**
**	r e a d _ a n d _ w r i t e _ f o r _ j o i n
**
*********************************************************************
**
**	Functional description:
**
**		Reads data from backup files and writes to standard
**		output file.
**
*********************************************************************
*/

	TEXT num_arr[5], total_arr[5];
	header_rec hdr_rec;

	FILE_DESC input_fl_desc = open(file_name, mode_read);

	if (input_fl_desc == -1) {
		fprintf(stderr, "can not open input file %s\n", file_name);
		return FB_FAILURE;
	}

	int read_cnt = read(input_fl_desc, *io_buffer, header_rec_len);
	if (read_cnt != static_cast<int>(header_rec_len)) {
		close(input_fl_desc);
		fprintf(stderr,
				   "progam fails to read gsplit header record in back-up file%s\n",
				   file_name);
		return FB_FAILURE;
	}

	const TEXT* char_ptr1 = reinterpret_cast<char*>(*io_buffer);
	SLONG ret_cd = strncmp(char_ptr1, header_rec_name, sizeof(hdr_rec.name) - 1);
	if (ret_cd != 0) {
		close(input_fl_desc);
		fprintf(stderr, "gsplit: expected GSPLIT description record\n");
		fprintf(stderr,
				   "gsplit: Exiting before completion due to errors\n");
		return FB_FAILURE;
	}

	SLONG skip_to_num = sizeof(hdr_rec.name) + sizeof(hdr_rec.date_time) +
		sizeof(hdr_rec.text1);
	SLONG skip_to_total = skip_to_num + sizeof(hdr_rec.num) + sizeof(hdr_rec.text2);

	char_ptr1 = reinterpret_cast<char*>(*io_buffer + skip_to_num);
	const TEXT* char_ptr2 = reinterpret_cast<char*>(*io_buffer + skip_to_total);
	size_t indx;
	for (indx = 0; indx < sizeof(hdr_rec.num); indx++) {
		num_arr[indx] = *char_ptr1;
		char_ptr1++;
		if (cnt == 1) {
			total_arr[indx] = *char_ptr2;
			char_ptr2++;
		}
	}
	num_arr[indx] = '\0';
	const SLONG num_int = atoi(num_arr);
	if (cnt == 1) {
		total_arr[indx] = '\0';
		*total_int = atoi(total_arr);
	}

	if ((num_int != cnt) || (num_int > *total_int)) {
		close(input_fl_desc);
		fprintf(stderr,
				   "gsplit: join backup file is out of sequence\n");
		fprintf(stderr,
				   "gsplit: Exiting before completion due to errors\n");
		return FB_FAILURE;
	}

	read_cnt = read(input_fl_desc, *io_buffer, IO_BUFFER_SIZE);


	while (true) {
		switch (read_cnt) {
		case (0):				// no more data to be read 
			close(input_fl_desc);
			return FB_SUCCESS;
			break;

		case (-1):				// read failed 
			close(input_fl_desc);
			return FB_FAILURE;
			break;

		default:				// this is the last read 
			break;
		}

		SLONG write_cnt = write(output_fl_desc, *io_buffer, read_cnt);

		switch (write_cnt) {
		case (-1):				// write failed 
			close(input_fl_desc);
			return FB_FAILURE;
			break;

		default:
			fb_assert(write_cnt == read_cnt);
			break;
		}

		read_cnt = read(input_fl_desc, *io_buffer, IO_BUFFER_SIZE);

	}	// end of while (true) loop
}


static int conv_ntoc( SLONG numeric_in, TEXT char_out[])
{
/********************************************************************
**
**	c o n v _ n t o c
**
*********************************************************************
**
**	Functional description:
**
**		Convert 4-digit numeric data to a 4-byte character array.
**
*********************************************************************
*/

	SLONG i = numeric_in;
	SLONG indx = 3;

	while (true) {
		const SLONG mod = i % 10;
		switch (mod) {
		case (0):
			char_out[indx] = '0';
			break;

		case (1):
			char_out[indx] = '1';
			break;

		case (2):
			char_out[indx] = '2';
			break;

		case (3):
			char_out[indx] = '3';
			break;

		case (4):
			char_out[indx] = '4';
			break;

		case (5):
			char_out[indx] = '5';
			break;

		case (6):
			char_out[indx] = '6';
			break;

		case (7):
			char_out[indx] = '7';
			break;

		case (8):
			char_out[indx] = '8';
			break;

		default:
			char_out[indx] = '9';
			break;
		}
		indx = indx - 1;
		i = i / 10;
		if (i <= 0)
			break;
	}

	for (; indx >= 0; indx--) {
		char_out[indx] = ' ';
	}
	return FB_SUCCESS;
}


static int write_header(b_fil*		fl_ptr,
						header_rec	hdr_rec,
						FILE_DESC	output_fl_desc,
						TEXT		header_str[])
{
/********************************************************************
**
**	w r i t e _ h e a d e r
**
*********************************************************************
**
**	Functional description:
**
**		writes out gsplit header record
**
**		assigns backup file name to header record file name array
**		calls conv_ntoc routine to convert numeric data to char data
**			and assigns to header record file number field
**		writes gsplit header record out to backup file
**
*********************************************************************
*/
	TEXT num_arr[5];
	SLONG end, indx;

	SLONG ret_cd = conv_ntoc(fl_ptr->b_fil_number, num_arr);
	if (ret_cd == FB_FAILURE) {
		printf
			("gsplit could not convert numeric data to character data\n");
		return FB_FAILURE;
	}
	num_arr[sizeof(num_arr) - 1] = TERMINAL;
	SLONG pos = sizeof(hdr_rec.name) + sizeof(hdr_rec.date_time) +
		sizeof(hdr_rec.text1);
	ret_cd = set_hdr_str(header_str, num_arr, pos, sizeof(hdr_rec.num));

	const TEXT* file_name = fl_ptr->b_fil_name;
	pos = sizeof(hdr_rec.name) + sizeof(hdr_rec.date_time) +
		sizeof(hdr_rec.text1) + sizeof(hdr_rec.num) +
		sizeof(hdr_rec.text2) + sizeof(hdr_rec.total) + sizeof(hdr_rec.text3);
	ret_cd = set_hdr_str(header_str, file_name, pos, strlen(file_name));

	SLONG write_cnt = write(output_fl_desc, header_str, header_rec_len);
	switch (write_cnt)
	{
	case (-1):					// write failed 
		close(output_fl_desc);
		return FB_FAILURE;
		break;

	default:
		end = pos + strlen(file_name);
		for (indx = pos; indx < end; indx++)
			header_str[indx] = BLANK;
		return FB_SUCCESS;
		break;
	}
}


static int flush_io_buff(UCHAR*		remaining_io,
						 SLONG		remaining_io_len,
						 FILE_DESC	output_fl_desc,
						 double		file_size,
						 SLONG*		byte_write,
						 bool*		flush_done)
{
/********************************************************************
**
**	f l u s h _ i o _ b u f f
**
*********************************************************************
**
** Functional description:
**
**	flush out the remaining data in the I/O buffer
**
**	when the file_size is truly has space to write out the remaining data
**		then
**			set and returns flush_done true
**		otherwise
**			closes the output file, set and returns flush_done false.
**	otherwise
**		we can only writes out as much data as file_size indicated
**		if it was able to write out the remaining data
**			then
**				set and returns flush_done true
**			otherwise
**				closes the output file, set and returns flush_done false.
**
*********************************************************************
*/

	SLONG write_cnt;

	if (file_size > remaining_io_len) {
		write_cnt = write(output_fl_desc, remaining_io, remaining_io_len);
	}
	else {						/* file_size <= remaining_io_len */

		write_cnt =
			write(output_fl_desc, remaining_io, (unsigned int) file_size);
	}

	switch (write_cnt) {
	case (-1):					// write failed 
		close(output_fl_desc);
		*flush_done = false;
		return FB_FAILURE;
		break;

	default:
		if (write_cnt == remaining_io_len)	// write ok 
			*flush_done = true;
		else {					// could not write out all remaining data 

			close(output_fl_desc);
			*flush_done = false;
		}
		*byte_write = write_cnt;
		return FB_SUCCESS;
		break;
	}
}


static int final_flush_io_buff(UCHAR * remaining_io,
							   SLONG remaining_io_len,
							   FILE_DESC output_fl_desc)
{
/********************************************************************
**
**	f i n a l _ f l u s h _ i o _ b u f f
**
*********************************************************************
**
** Functional description:
**
**	flush out the remaining data in the I/O buffer
**
*********************************************************************
*/

	SLONG write_cnt = write(output_fl_desc, remaining_io, remaining_io_len);
	switch (write_cnt)
	{
	case (-1):					// write failed 
		close(output_fl_desc);
		return FB_FAILURE;
		break;

	default:
		if (write_cnt == remaining_io_len)	// write ok 
			return FB_SUCCESS;
		else {					// could not write out all remaining data 

			close(output_fl_desc);
			return FB_FAILURE;
		}
		break;
	}
}


static int print_clo(const TEXT* prog_name)
{
/********************************************************************
**
**	p r i n t _ c l o
**
*********************************************************************
**
**	Functional description:
**
**		print out gsplit utility command line options
**
*********************************************************************
*/

	fprintf(stderr, "%s: Command Line Options Are:\n", prog_name);
	fprintf(stderr,
			   "  gsplit -S[PLIT_BK_FILE] <file> <size>{k|m|g} [... <file> [<size>{k|m|g}]] or\n");
	fprintf(stderr, "  gsplit -J[OINT_BK_FILE] <file> [... <file>]\n");
	fprintf(stderr,
			   "%s: option can be abbreviated to the unparenthesized characters\n",
			   prog_name);
	fprintf(stderr,
			   "%s: Exiting before completion due to errors\n", prog_name);

	return FB_SUCCESS;

}


static int set_hdr_str(TEXT header_str[], const TEXT * in_str, SLONG pos, SLONG len)
{
/********************************************************************
**
**	s e t _ h d r _ s t r
**
*********************************************************************
**
**	Functional description:
**
**		initialyze header string
**
*********************************************************************
*/
	const TEXT* t_str = in_str;
	const SLONG end = pos + len;

	for (SLONG indx = pos; indx < end; indx++) {
		switch (*t_str) {
		case (NEW_LINE):
		case (TERMINAL):
			header_str[indx] = ' ';
			break;
		default:
			header_str[indx] = *t_str;
			break;
		}
		*t_str++;
	}
	return FB_SUCCESS;
}


static int free_file_list( b_fil* file_list)
{
/********************************************************************
**
**	f r e e _ f i l e _ l i s t
**
*********************************************************************
**
**	Functional description:
**
**		free all the storage allocated for backup files structure
**
*********************************************************************
*/

	b_fil* next_file = NULL;

	for (b_fil* file_ptr = file_list; file_ptr != NULL; file_ptr = next_file) {
		next_file = file_ptr->b_fil_next;
		free(file_ptr->b_fil_name);
		free(file_ptr);
	}

	return FB_SUCCESS;
}

