/*
 * main.c  -  Systems database utility for netboot
 *
 * Copyright (C) 2006-2007 Gero Kuhlmann   <gero@gkminix.han.de>
 *
 *  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; either version 2 of the License, or
 *  any later version.
 *
 *  This program is distributed in the hope that it will 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 to the Free Software
 *  Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
 *
 * $Id: main.c,v 1.7 2007/01/06 18:30:50 gkminix Exp $
 */

#include <common.h>
#include <nblib.h>


/*
 * Definition of actions
 */
#define ACTION_ERROR	-1
#define ACTION_NONE	 0
#define ACTION_LIST	 1
#define ACTION_PRINT	 2
#define ACTION_DELETE	 3
#define ACTION_CLONE	 4



/*
 * Default filter for listing database record names
 */
#define DFLT_FILTER	"*:*"



/*
 * Local variables
 */
static char *listfilter = NULL;		/* Filter for database record listing */
static char *clonedb = NULL;		/* Name of database to clone */
static char *printname = NULL;		/* Name of record to print */
static char *delname = NULL;		/* Name of record to delete */
static char *outfile = NULL;		/* Name of output file */
static int listflag = FALSE;		/* Non-zero to list record names */
static int rawflag = FALSE;		/* Non-zero to print raw records */
static LISTHDL paramlist = LISTHDL_NULL;	/* List of record parameters */



/*
 * Copyright information
 */
static char *copyright[] = {
	COPYRIGHT,
	NULL
};



/*
 * Command line options and arguments
 */
static struct cmdopt opts[] = {
	{ NULL, 0, copyrightmsg, {copyright}, NULL, NULL		},
	{ "list", 'l', boolval, {&listflag},
	  "list all database record names", NULL			},
	{ "filter", 'f', strval, {&listfilter},
	  "list only selected database record names", "FILTER"		},
	{ "print", 'p', strval, {&printname},
	  "print contents of database record", "NAME"			},
	{ "delete", 'd', strval, {&delname},
	  "delete database record", "NAME"				},
	{ "clone", 'c', strval, {&clonedb},
	  "clone a database", "FILE|DB"					},
	{ "outfile", 'o', strval, {&outfile},
	  "output file name", "FILE"					},
	{ "raw", 'r', boolval, {&rawflag},
	  "print raw entry parameters with print action", NULL		},
	{ NULL, 0, noval, {NULL}, NULL, NULL				}
};




/*
 **************************************************************************
 *
 *		Misc. helper routines
 *
 **************************************************************************
 */

/*
 * Print an error message in case of output error
 */
static void checkerr __F((errnum), int errnum)
{
  if (errnum < 0) {
	prnerr("Unable to write to output file %s",
			(outfile == NULL ? "<stdout>" : outfile));
	nbexit(EXIT_WRITE);
  }
}




/*
 **************************************************************************
 *
 *		Routines to read parameters for one record
 *
 **************************************************************************
 */

/*
 * Structure holding parameter data
 */
struct paramdata {
	struct paramdef def;
	union {
		char *strval;
		long  longval;
		int   boolval;
	} val;
};



/*
 * Compare a parameter name
 */
static int paramcmp __F((handle, data, state),
					LISTHDL handle AND
					voidstar *data AND
					const voidstar state)
{
  struct paramdata *p = *((struct paramdata **)data);

  return(handle == paramlist && p != NULL && !strcmp(p->def.name, (char *)state));
}



/*
 * Add one single parameter into linked list
 */
static char *paramadd __F((name, value, encode),
					char *name AND
					char **value AND
					int encode)
{
  struct paramdata **pp, *p = NULL;
  int pos;

  if (!encode && paramlist != LISTHDL_NULL) {
	pos = (rawflag ? -1 : walklist(paramlist, &paramcmp, (voidstar)name));
	if (pos < 0) {
		p = (struct paramdata *)nbmalloc(sizeof(struct paramdata));
		(void)appendlist(paramlist, (voidstar)p);
		p->def.type = par_null;
	} else if ((pp = (struct paramdata **)getatlist(paramlist, pos)) != NULL) {
		p = *pp;
	}
	if (p != NULL) {
		long l;
		char *cp, *val = *value;

		/* Setup parameter name */
		copystr(&(p->def.name), name);

		/* Delete any previous values */
		if (p->def.type == par_string && p->val.strval != NULL) {
			free(p->val.strval);
			p->val.strval = NULL;
		}
		p->def.type = par_null;

		/* Check for empty parameter value */
		if (val == NULL || !*val)
			return(NULL);

		/* Check if this is a boolean TRUE value */
		if (!strcmp(val, "TRUE") || !strcmp(val, "true")) {
			p->def.type = par_bool;
			p->def.valptr.boolptr = &(p->val.boolval);
			p->val.boolval = TRUE;
			return(NULL);
		}

		/* Check if this is a boolean FALSE value */
		if (!strcmp(val, "FALSE") || !strcmp(val, "false")) {
			p->def.type = par_bool;
			p->def.valptr.boolptr = &(p->val.boolval);
			p->val.boolval = FALSE;
			return(NULL);
		}

		/* Check if this is a numeric value */
		errno = 0;
		l = strtol(val, &cp, 0);
		if (!*cp && errno == 0) {
			p->def.type = par_long;
			p->def.valptr.longptr = &(p->val.longval);
			p->val.longval = l;
			return(NULL);
		}

		/* Everything else is to be copied verbatim as a string value */
		p->def.type = par_string;
		p->def.valptr.strptr = &(p->val.strval);
		copystr(&(p->val.strval), val);
	}
  }
  return(NULL);
}



/*
 * Parameter structure for entry reading routine
 */
static struct paramdef readparamdef = {
  NULL, par_proc, NULL, { (voidstar)&paramadd }
};



/*
 * Routine to destroy one parameter record
 */
static void paramfree __F((handle, data), LISTHDL handle AND voidstar data)
{
  struct paramdata *p = (struct paramdata *)data;

  if (handle == paramlist && p != NULL) {
	if (p->def.name != NULL)
		free(p->def.name);
	if (p->def.type == par_string && p->val.strval != NULL)
		free(p->val.strval);
	free(p);
  }
}



/*
 * Read all parameters for one record
 */
static int readparams __F((recname), char *recname)
{
  paramlist = createlist(&paramfree);
  if (!readsysdb(recname, &readparamdef)) {
	releaselist(paramlist);
	paramlist = LISTHDL_NULL;
	return(FALSE);
  }
  return(TRUE);
}




/*
 **************************************************************************
 *
 *		Routines to display one record
 *
 **************************************************************************
 */

/*
 * Print one single parameter from linked list
 */
static int paramprint __F((handle, data, state),
					LISTHDL handle AND
					voidstar *data AND
					const voidstar state)
{
  struct paramdata *p = *((struct paramdata **)data);

  if (p != NULL && (rawflag || p->def.type != par_null)) {
	checkerr(fprintf((FILE *)state, "  %s = ", p->def.name));
	switch (p->def.type) {
		case par_long:
			checkerr(fprintf((FILE *)state, "%ld\n", p->val.longval));
			break;
		case par_bool:
			checkerr(fprintf((FILE *)state, "%s\n",
						(p->val.boolval ? "TRUE" : "FALSE")));
			break;
		case par_string:
			checkerr(fprintf((FILE *)state, "\"%s\"\n", p->val.strval));
			break;
		default:
			checkerr(fprintf((FILE *)state, "\n"));
			break;
	}
  }
  return(FALSE);
}



/*
 * Print one record in the systems database
 */
static void doprint __F((recname, outf), char *recname AND FILE *outf)
{
  /* Read all parameters into a linked list */
  if (!opensysdb(TRUE))
	nbexit(-1);
  if (!readparams(recname))
	nbexit(-1);
  closesysdb();

  /* Now print whole list */
  checkerr(fprintf(outf, "[%s]\n", recname));
  (void)walklist(paramlist, &paramprint, (voidstar)outf);
  releaselist(paramlist);
  paramlist = LISTHDL_NULL;
}




/*
 **************************************************************************
 *
 *		Routines to delete one record
 *
 **************************************************************************
 */

/*
 * Delete one record in the systems database
 */
static void dodelete __F((recname), const char *recname)
{
  if (!opensysdb(FALSE))
	nbexit(-1);
  if (!delsysdb(recname))
	nbexit(-1);
  closesysdb();
}




/*
 **************************************************************************
 *
 *		Routines to display record name list
 *
 **************************************************************************
 */

/*
 * Routine to print out the name of one record
 */
static int listprint __F((handle, data, state),
					LISTHDL handle AND
					voidstar *data AND
					const voidstar state)
{
  checkerr(fprintf((FILE *)state, "%s\n", *((char **)data)));
  return(FALSE);
}



/*
 * List all entries in the systems database
 */
static void dolist __F((filter, outf), const char *filter AND FILE *outf)
{
  LISTHDL reclist;
  int count;

  /* Generate list of record names */
  if (!opensysdb(TRUE))
	nbexit(-1);
  if ((reclist = listsysdb(filter)) == LISTHDL_NULL)
	nbexit(-1);
  closesysdb();
  if ((count = getcountlist(reclist)) == 0) {
	prnlog(LOGLEVEL_NORMAL, "No records in systems database\n");
	releaselist(reclist);
	return;
  }

  /* Print the list of records in the systems database */
  (void)walklist(reclist, &listprint, (voidstar)outf);
  if (logfd(LOGLEVEL_NORMAL) == outf)
	checkerr(fprintf(outf, "\n"));
  prnlog(LOGLEVEL_NORMAL, "Number of records: %d\n", count);
  releaselist(reclist);
}




/*
 **************************************************************************
 *
 *		Routines to clone a database
 *
 **************************************************************************
 */

/*
 * Parameter definition array
 */
static struct paramdef *cloneparams = NULL;
static int clonecount = 0;
static int cloneindex = 0;



/*
 * Routine to add one parameter to the parameter definition array
 */
static int cloneadd __F((handle, data, state),
					LISTHDL handle AND
					voidstar *data AND
					const voidstar state)
{
  struct paramdata *p = *((struct paramdata **)data);

  if (cloneindex < clonecount) {
	memcpy(&(cloneparams[cloneindex]), &(p->def), sizeof(struct paramdef));
	cloneindex++;
  }
  return(FALSE);
}



/*
 * Routine to copy one entry from input database into systems database
 */
static int clonerecord __F((handle, data, state),
					LISTHDL handle AND
					voidstar *data AND
					const voidstar state)
{
  struct sectdef sects[2];
  DBHDL dbhandle = *((DBHDL *)state);
  char *recname = *((char **)data);
  int ret;

  /* Add a little safety */
  assert(cloneparams == NULL && clonecount == 0 && paramlist == LISTHDL_NULL);
  prnlog(LOGLEVEL_NOTICE, "Cloning record '%s'\n", recname);

  /* Create a section definition */
  memzero(sects, sizeof(sects));
  sects[0].name = recname;
  sects[0].params = &readparamdef;
  sects[0].startsect = NULL;
  sects[0].endsect = NULL;

  /* Read all parameters into a linked list */
  paramlist = createlist(&paramfree);

  /* Search for record name */
  if (!findfirst(dbhandle, recname)) {
	if (dberrors == 0)
		nberror(EXIT_DB,
			"unable to find record \"%s\" in input database",
			recname);
	else
		nberror(EXIT_DB, NULL);
clonerecerr:
	releaselist(paramlist);
	paramlist = LISTHDL_NULL;
	return(TRUE);
  }

  /* Read all records for this system */
  do {
	if (readrec(dbhandle, sects) != 1) {
		if (dberrors == 0)
			nberror(EXIT_DB,
				"invalid number of records for \"%s\"",
				recname);
		else
			nberror(EXIT_DB, NULL);
		goto clonerecerr;
	}
  } while (findnext(dbhandle));
  if (dberrors > 0) {
	nberror(EXIT_DB, NULL);
	goto clonerecerr;
  }

  /* Check if we have anything to clone at all */
  if ((clonecount = getcountlist(paramlist)) == 0) {
	releaselist(paramlist);
	paramlist = LISTHDL_NULL;
	return(FALSE);
  }

  /* Create parameter definition array */
  cloneparams = (struct paramdef *)nbmalloc(sizeof(struct paramdef) * (clonecount + 1));
  cloneindex = 0;

  /* Now walk through parameter list to create definition array */
  (void)walklist(paramlist, &cloneadd, NULL);
  assert(cloneindex == clonecount);

  /* Add the final record definition */
  cloneparams[cloneindex].name = NULL;
  cloneparams[cloneindex].type = par_null;

  /* Write all parameters into the systems database */
  ret = writesysdb(recname, cloneparams);

  /* Release the list of parameters */
  free(cloneparams);
  cloneparams = NULL;
  clonecount = 0;
  releaselist(paramlist);
  paramlist = LISTHDL_NULL;
  return(!ret);
}



/*
 * Compare a record name
 */
static int clonecmp __F((handle, data, state),
					LISTHDL handle AND
					voidstar *data AND
					const voidstar state)
{
  return(!strcmp(*((char **)data), (char *)state));
}



/*
 * Clone a database or file into the systems database
 */
static void doclone __F((dbname), const char *dbname)
{
  DBHDL dbhandle;
  LISTHDL reclist;
  int count, dbstate;
  char *absname = NULL;
  char *recname;

  /* Create input database handle with absolute file name */
  if (strncmp(dbname, "db@", 3) != 0 && *dbname != '/') {
	absname = (char *)nbmalloc(strlen(dbname) + strlen(curworkdir) + 2);
	sprintf(absname, "%s/%s", curworkdir, dbname);
  } else
	copystr(&absname, dbname);
  dbhandle = createdb(absname, NULL);
  free(absname);
  if (dbhandle == DBHDL_NULL)
	nbexit(-1);

  /* Open input database */
  if (opendb(dbhandle, TRUE) == 0 || dberrors > 0) {
	if (dberrors == 0)
		nberror(EXIT_DB, "unable to open database '%s'", dbname);
	else
		nberror(EXIT_DB, NULL);
	freedb(dbhandle);
	nbexit(-1);
  }

  /* Generate list of record names in input file or database */
  prnlog(LOGLEVEL_DEBUG, "Reading list of record names from '%s'\n", dbname);
  reclist = createlist(NULL);
  if (findfirst(dbhandle, "*:*")) {
	do {
		recname = NULL;
		dbstate = getstatedb(dbhandle, &recname) &
						(DBSTATUS_VALID | DBSTATUS_POS);
		if (recname != NULL) {
			if (dbstate != (DBSTATUS_VALID | DBSTATUS_POS))
				free(recname);
			else if (walklist(reclist, &clonecmp, (voidstar)recname) < 0)
				(void)appendlist(reclist, recname);
		}
	} while (findnext(dbhandle));
  }
  if (dberrors > 0) {
	nberror(EXIT_DB, NULL);
cloneerr:
	releaselist(reclist);
	closedb(dbhandle);
	freedb(dbhandle);
	nbexit(-1);
  }

  /* Walk through all records and clone them into the systems database */
  if (!opensysdb(FALSE))
	goto cloneerr;
  if ((count = getcountlist(reclist)) == 0) {
	prnlog(LOGLEVEL_NOTICE, "No records found in '%s' to clone\n", dbname);
	goto cloneend;
  }
  if (walklist(reclist, &clonerecord, &dbhandle) >= 0) {
	closesysdb();
	goto cloneerr;
  }
  prnlog(LOGLEVEL_DEBUG, "Number of records cloned into systems database: %d\n", count);

  /* Clean up at the end */
cloneend:
  releaselist(reclist);
  closedb(dbhandle);
  freedb(dbhandle);
  closesysdb();
}




/*
 **************************************************************************
 *
 *		Main program routine
 *
 **************************************************************************
 */

/*
 * Main program
 */
int main __F((argc, argv), int argc AND char **argv)
{
  FILE *outf;
  int action;

  /* Parse options and read configuration file */
  nbsetup(argc, argv, opts, NULL);

  /* Determine type of action to perform */
  action = ACTION_NONE;
  if (listflag) {
	if (printname != NULL || delname != NULL || listfilter != NULL || clonedb != NULL)
		action = ACTION_ERROR;
	else {
		action = ACTION_LIST;
		copystr(&listfilter, DFLT_FILTER);
	}
  } else if (listfilter != NULL) {
	if (printname != NULL || delname != NULL || clonedb != NULL)
		action = ACTION_ERROR;
	else
		action = ACTION_LIST;
  } else if (printname != NULL) {
	if (delname != NULL || clonedb != NULL)
		action = ACTION_ERROR;
	else
		action = ACTION_PRINT;
  } else if (clonedb != NULL) {
	if (delname != NULL)
		action = ACTION_ERROR;
	else
		action = ACTION_CLONE;
  } else if (delname != NULL)
	action = ACTION_DELETE;

  /* Check for errors on the command line */
  if (action == ACTION_ERROR) {
	prnerr("only one action can be specified");
	nbexit(EXIT_USAGE);
  } else if (action == ACTION_NONE) {
	prnerr("no or invalid action specified");
	nbexit(EXIT_USAGE);
  } else if (rawflag && action != ACTION_PRINT) {
	prnerr("raw option only allowed with print action");
	nbexit(EXIT_USAGE);
  } else if (outfile != NULL &&
             action != ACTION_LIST && action != ACTION_PRINT) {
	prnerr("output file specification not allowed");
	nbexit(EXIT_USAGE);
  }

  /* Open the output file if requested */
  if (outfile != NULL) {
	if (!strcmp(outfile, "-"))
		outf = stdout;
	else if ((outf = fopen(outfile, "w")) == NULL) {
		prnerr("unable to open output file '%s'", outfile);
		nbexit(EXIT_OPEN);
	}
  } else
	outf = stdout;

  /* Now perform action */
  switch (action) {
	case ACTION_LIST:
		dolist(listfilter, outf);
		break;
	case ACTION_PRINT:
		doprint(printname, outf);
		break;
	case ACTION_DELETE:
		dodelete(delname);
		break;
	case ACTION_CLONE:
		doclone(clonedb);
		break;
  }

  /* Close the output file before exit */
  if (logfd(LOGLEVEL_NORMAL) != outf)
	prnlog(LOGLEVEL_NORMAL, "Output written to '%s'\n", (outfile == NULL ? "<stdout>" : outfile));
  if (outf != stdout)
	(void)fclose(outf);
  nbexit(EXIT_SUCCESS);
#if !defined(__GNUC__) || __GNUC__ < 3
  /* Make the compiler happy, GCC knows that nbexit() never returns */
  return(0);
#endif
}

