/*
 * Copyright (c) 2000-2002 Silicon Graphics, Inc.
 * All Rights Reserved.
 *
 * This program is free software; you can redistribute it and/or
 * modify it under the terms of the GNU General Public License as
 * published by the Free Software Foundation.
 *
 * This program is distributed in the hope that it would be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write the Free Software Foundation,
 * Inc.,  51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
 */

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/stat.h>
#include <time.h>
#include <assert.h>
#include <string.h>
#include <uuid/uuid.h>

#include "config.h"

#include "types.h"
#include "mlog.h"
#include "dlog.h"
#include "path.h"
#include "getopt.h"
#include "global.h"
#include "drive.h"

/* drive.c - selects and initializes a drive strategy
 */


/* structure definitions used locally ****************************************/


/* declarations of externally defined global symbols *************************/

extern void usage(void);
extern char *homedir;

/* declare all drive strategies here
 */
extern drive_strategy_t drive_strategy_simple;
extern drive_strategy_t drive_strategy_scsitape;
extern drive_strategy_t drive_strategy_rmt;


/* forward declarations of locally defined static functions ******************/

static drive_t *drive_alloc(char *, size_t);
static void drive_allochdrs(drive_t *drivep,
			     global_hdr_t *gwhdrtemplatep,
			     ix_t driveix);


/* definition of locally defined global variables ****************************/

drive_t **drivepp;
size_t drivecnt;
size_t partialmax;

/* definition of locally defined static variables *****************************/

/* drive strategy array - ordered by precedence
 */
static drive_strategy_t *strategypp[] = {
	&drive_strategy_simple,
	&drive_strategy_scsitape,
	&drive_strategy_rmt,
};


/* definition of locally defined global functions ****************************/

/* drive_init1 - select and instantiate a drive manager for each drive
 * specified on the command line.
 */
bool_t
drive_init1(int argc, char *argv[])
{
	int c;
	ix_t driveix;

	/* sanity check asserts
	 */
	assert(sizeof(drive_hdr_t) == DRIVE_HDR_SZ);

	/* count drive arguments
	 */
	optind = 1;
	opterr = 0;
	drivecnt = 0;
	while ((c = getopt(argc, argv, GETOPT_CMDSTRING)) != EOF) {
		switch (c) {
		case GETOPT_DUMPDEST:
			drivecnt++;
			break;
		}
	}

	/* allocate an array to hold ptrs to drive descriptors
	 */
	if (drivecnt > 0) {
		drivepp = (drive_t **)calloc(drivecnt, sizeof(drive_t *));
		assert(drivepp);
	}

	/* initialize the partialmax value.  Each drive can be completing a file
	 * started in another drive (except for drive 0) and leave one file to
	 * be completed by another drive.  This value is used to limit the
	 * search in the list of partially completed files shared between all
	 * restore streams.  Note, if drivecnt is one, then partialmax is zero
	 * to indicate no partial files can span streams.
	 */
	partialmax = (drivecnt <= 1 ? 0 : (drivecnt * 2) - 1);

	/* initialize drive descriptors from command line arguments
	 */
	optind = 1;
	opterr = 0;
	driveix = 0;
	while ((c = getopt(argc, argv, GETOPT_CMDSTRING)) != EOF) {
		switch (c) {
		case GETOPT_DUMPDEST:
			if (!optarg || optarg[0] == '-') {
				mlog(MLOG_NORMAL,
				      _("-%c argument missing\n"),
				      c);
				usage();
				return BOOL_FALSE;
			}

			/* allocate a drive descriptor
			 */
			drivepp[driveix] = drive_alloc(optarg, driveix);
			driveix++;
			break;
		}
	}
	assert(driveix == drivecnt);

	/* the user may specify stdin as the source, by
	 * a single dash ('-') with no option letter. This must appear
	 * between all lettered arguments and the file system pathname.
	 */
	if (optind < argc && !strcmp(argv[optind ], "-")) {
		if (driveix > 0) {
			mlog(MLOG_NORMAL,
#ifdef DUMP
			_("cannot specify source files and stdout together\n")
#endif /* DUMP */
#ifdef RESTORE
			_("cannot specify source files and stdin together\n")
#endif /* RESTORE */
			      );
			usage();
			return BOOL_FALSE;
		}

		drivecnt = 1;

		/* Adding this alloc to fix malloc corruption.
		 * Bug #393618 - prasadb 04/16/97
		 * allocate an array to hold ptrs to drive descriptors
		 */
		drivepp = (drive_t **)calloc(drivecnt, sizeof(drive_t *));
		assert(drivepp);

		drivepp[0 ] = drive_alloc("stdio", 0);

#ifdef DUMP   /* ifdef added around dlog_desist() by prasadb to fix 435626 */
		dlog_desist();
#endif
	}

	/* verify that some dump destination(s) / restore source(s) specified
	 */
	if (drivecnt == 0) {
		mlog(MLOG_NORMAL | MLOG_ERROR,
#ifdef DUMP
			_("no destination file(s) specified\n")
#endif /* DUMP */
#ifdef RESTORE
			_("no source file(s) specified\n")
#endif /* RESTORE */
			);
		usage();
		return BOOL_FALSE;
	}

	/* run each drive past each strategy, pick the best match
	 * and instantiate a drive manager.
	 */
	for (driveix = 0; driveix < drivecnt; driveix++) {
		drive_t *drivep = drivepp[driveix];
		int bestscore = 0 - INTGENMAX;
		ix_t six;
		ix_t scnt = sizeof(strategypp) / sizeof(strategypp[0]);
		drive_strategy_t *bestsp = 0;
		bool_t ok;

		for (six = 0; six < scnt; six++) {
			drive_strategy_t *sp = strategypp[six];
			int score;
			score = (*sp->ds_match)(argc,
						    argv,
						    drivep);
			if (!bestsp || score > bestscore) {
				bestsp = sp;
				bestscore = score;
			}
		}
		assert(bestsp);
		drivep->d_strategyp = bestsp;
		drivep->d_recmarksep = bestsp->ds_recmarksep;
		drivep->d_recmfilesz = bestsp->ds_recmfilesz;
		mlog(MLOG_VERBOSE,
		      _("using %s strategy\n"),
		      bestsp->ds_description);
		ok = (*bestsp->ds_instantiate)(argc,
						   argv,
						   drivep);
		if (!ok) {
			return BOOL_FALSE;
		}
	}

	return BOOL_TRUE;
}


/* drive_init2 - second phase strategy initialization.
 * allocates global read and write hdrs, copying global hdr template
 * into the write hdrs (DUMP only). kicks off async init for each drive,
 * which will be synchronized with drive_init3.
 */
/* ARGSUSED */
bool_t
drive_init2(int argc,
	     char *argv[],
	     global_hdr_t *gwhdrtemplatep)
{
	ix_t driveix;

	for (driveix = 0; driveix < drivecnt; driveix++) {
		drive_t *drivep = drivepp[driveix];
		bool_t ok;

		drive_allochdrs(drivep, gwhdrtemplatep, driveix);
		ok = (*drivep->d_opsp->do_init)(drivep);
		if (!ok) {
			return BOOL_FALSE;
		}
	}

	return BOOL_TRUE;
}


/* drive_init3 - third phase strategy initialization.
 * synchronizes with async operations begun by drive_init2.
 */
bool_t
drive_init3(void)
{
	ix_t driveix;

	for (driveix = 0; driveix < drivecnt; driveix++) {
		drive_t *drivep = drivepp[driveix];
		bool_t ok;

		ok = (*drivep->d_opsp->do_sync)(drivep);
		if (!ok) {
			return BOOL_FALSE;
		}
	}

	return BOOL_TRUE;
}


/* drive_mark_commit - commits and unlinks all accumulated marks with
 * offsets less than or equal to the offset of the next (as yet unwritten)
 * byte in the media file.
 * utility function for use by drive-specific strategies.
 */
void
drive_mark_commit(drive_t *drivep, off64_t ncommitted)
{
	drive_markrec_t *dmp;

	for (dmp = drivep->d_markrecheadp
	;
	dmp && dmp->dm_log <= (drive_mark_t)ncommitted
	;
	) {
		drivep->d_markrecheadp = dmp->dm_nextp;
		(*dmp->dm_cbfuncp)(dmp->dm_cbcontextp, dmp, BOOL_TRUE);
		dmp = drivep->d_markrecheadp;
	}
}

/* drive_mark_discard - unlinks all accumulated marks, calling their callbacks
 * indicating the mark was NOT committed.
 * utility function for use by drive-specific strategies.
 */
void
drive_mark_discard(drive_t *drivep)
{
	drive_markrec_t *dmp;

	for (dmp = drivep->d_markrecheadp
	;
	dmp
	;
	drivep->d_markrecheadp = dmp->dm_nextp, dmp = dmp->dm_nextp) {

		(*dmp->dm_cbfuncp)(dmp->dm_cbcontextp, dmp, BOOL_FALSE);
	}
}

/* drive_display_metrics - called by main thread during interactive dialog
 * to print drive throughput and streaming metrics.
 */
void
drive_display_metrics(void)
{
	ix_t driveix;

	for (driveix = 0; driveix < drivecnt; driveix++) {
		drive_t *drivep = drivepp[driveix];
		drive_ops_t *dop = drivep->d_opsp;
		if (dop->do_display_metrics) {
			(*dop->do_display_metrics)(drivep);
		}
	}
}


/* definition of locally defined static functions ****************************/

/* drive_alloc - allocate and initialize the generic portions of a drive
 * descriptor. do NOT allocate hdr buffers.
 */
static drive_t *
drive_alloc(char *pathname, ix_t driveix)
{
	drive_t *drivep;
	struct stat64 statbuf;

	/* allocate the descriptor
	 */
	drivep = (drive_t *)calloc(1, sizeof(drive_t));
	assert(drivep);

	/* convert the pathname to an absolute pathname
	 * NOTE: string "stdio" is reserved to mean send to standard out
	 */
	if (strcmp(pathname, "stdio")) {
		pathname = path_reltoabs(pathname, homedir);
	}

	/* set pipe flags
	 */
	if (!strcmp(pathname, "stdio")) {
		drivep->d_isunnamedpipepr = BOOL_TRUE;
	} else if (!stat64(pathname, &statbuf)
		    &&
		    (statbuf.st_mode & S_IFMT) == S_IFIFO) {
		drivep->d_isnamedpipepr = BOOL_TRUE;
	}

	/* complete the drive manager
	 */
	drivep->d_pathname = pathname;
	drivep->d_index = driveix;

	return drivep;
}

/* drive_allochdrs - allocate and initialize the drive read and write
 * hdrs, and ptrs into the hdrs.
 */
static void
drive_allochdrs(drive_t *drivep, global_hdr_t *gwhdrtemplatep, ix_t driveix)
{
	global_hdr_t *grhdrp;
	drive_hdr_t *drhdrp;
	global_hdr_t *gwhdrp;
	drive_hdr_t *dwhdrp;

	/* allocate the read header
	 */
	grhdrp = (global_hdr_t *)calloc(1, sizeof(global_hdr_t));
	assert(grhdrp);
	gwhdrp = NULL;
	dwhdrp = NULL;

	/* calculate pointer to the drive portion of the read header
	 */
	drhdrp = (drive_hdr_t *)grhdrp->gh_upper;

	/* global write hdr used only for dumps. will be NULL for restore
	 */
	if (gwhdrtemplatep) {
		/* allocate the write header
		 */
		gwhdrp = (global_hdr_t *)calloc(1, sizeof(global_hdr_t));
		assert(gwhdrp);

		/* copy the template
		 */
		*gwhdrp = *gwhdrtemplatep;

		/* calculate pointer to the drive portion of the read header
		 */
		dwhdrp = (drive_hdr_t *)gwhdrp->gh_upper;

		/* fill in generic drive fields of write hdr
		 */
		dwhdrp->dh_strategyid = drivep->d_strategyp->ds_id;
		dwhdrp->dh_driveix = driveix;
		dwhdrp->dh_drivecnt = drivecnt;
	}

	/* complete the drive manager
	 */
	drivep->d_greadhdrp = grhdrp;
	drivep->d_readhdrp = drhdrp;
	drivep->d_gwritehdrp = gwhdrp;
	drivep->d_writehdrp = dwhdrp;
}
