/**************************************************************************
 * Copyright 1994-2003 Patrick Powell, San Diego, CA <papowell@astart.com>
 **************************************************************************/
/**** HEADER *****/
#include "patchlevel.h"
 static char *const _id =
  "Version " PATCHLEVEL "\n"
  "$Id: ifhp.c,v 1.105 2004/09/24 20:18:13 papowell Exp papowell $"
  "\n"
  "Copyright 1992-2002 Patrick Powell <papowell@astart.com>"
  "\n"
  ;

#include "ifhp.h"
#include "debug.h"

/*
 * Main program:
 * 1. does setup - checks mode
 * 2. initializes connection
 * 3. initializes printer
 * 4. checks printer for idle status
 * 5. gets pagecount
 * 6.
 *   if IF mode {
 *       determines job format
 *         - binary, PCL, PostScript, Text
 *       if PCL then sets up PCL
 *       if PostScript then sets up PostScript
 *       if Text then
 *          sets up Text
 *          if text converter,  runs converter
 *       puts job out to printer
 *   }
 *   if OF mode {
 *       reads input line;
 *       if suspend string, then suspend
 *       else pass through
 *   }
 * 7. terminates job
 * 8. checks printer for end of job status
 * 9. gets page count
 */


 char *Outbuf;	/* buffer */
 int Outmax;		/* max buffer size */
 int Outlen;		/* length to output */
 char *Inbuf;	/* buffer */
 int Inmax;		/* max buffer size */
 int Inlen;		/* total input */

 char *PJL_UEL_str =  "\033%-12345X";
 char *PJL_str =  "@PJL\n";
 char *PJL_INFO_PAGECOUNT_str = "@PJL INFO PAGECOUNT \n";
 char Jobname[SMALLBUFFER];
 char Job_ready_msg[SMALLBUFFER];
 char End_ready_msg[SMALLBUFFER];
 static void Pjl_displayname(void);

 int main(int argc,char *argv[], char *envp[])
{
	char errmsg[SMALLBUFFER];
	struct stat statb;
	char *s, *dump, *pc_entry;
	int i, fd, c, do_pagecount, pagecount_ps, pagecount_pjl, pagecount_snmp;
	char *pagecount_prog;

	/* make sure that the NSLPATH cannot be use to compromisze root
	 * run executables
	 */

	do_pagecount = pagecount_ps = pagecount_pjl = 0;
	pagecount_prog = 0;
	Trace_on_stderr = 0;
	Debug = 0;
	dump = 0;
	pc_entry = 0;
	/* Parse_debug("4,database+3",1); */

    /* FATAL(LOGINFO)"%s",1); */

	Argc = argc;
	Argv = argv;
	Envp = envp;
	if( argc ){
		Name = argv[0];
		if( (s = safestrrchr( Name, '/' )) ){
			Name = s+1;
		}
	} else {
		Name = "????";
	}
	/* initialize all of the OBJ global variables */
	{
		OBJ ***list, **var;
		for( list = Var_list; (var = *list); ++list ){
			*var = NEW_OBJ(0,MEMINFO);
		}
	}


	/* paranoia,  or even tripletanoia */
    if( (getuid() == 0 || geteuid() == 0) && getenv("NLSPATH") ){
#if defined(HAVE_UNSETENV)
		unsetenv("NLSPATH");
#elif defined(HAVE_SETENV)
		setenv("NLSPATH","",1);
#elif defined(HAVE_PUTENV)
		putenv("NLSPATH=");
#else
#error need to have one of unsetenv(), setenv(), or putenv()
#endif
	}

#if HAVE_LOCALE_H
	setlocale(LC_ALL, "");
#endif

	bindtextdomain (PACKAGE, LOCALEDIR);
	textdomain (PACKAGE);

	time( &Start_time );
	/* do setup */

	/* set up the accounting FD for output */
	if( fstat(0,&statb) == -1 ){
		Errorcode = JABORT;
		FATAL(LOGINFO) _("ifhp: stdin is not open"));
	}
	if( fstat(1,&statb) == -1 ){
		Errorcode = JABORT;
		FATAL(LOGINFO) _("ifhp: stdout is not open"));
	}
	if( fstat(2,&statb) == -1 ){
		if( (fd = open( "/dev/null", O_WRONLY )) != 2 ){
			Errorcode = JABORT;
			LOGERR_DIE(LOGINFO) _("ifhp: open /dev/null failed") );
		}
	}

	/* initialize input and output buffers */
	Init_outbuf();
	Init_inbuf();
	
	/*
	 * The order of processing of options is:
	 * a) printcap - config extracted
	 * b) printcap - ifhp=... from printcap processed
	 * b) command line -T options extracted
	 * c) value of 'config' and 'model' found from current set of options
	 * d) ifhp.conf read for model information
	 * e) printcap (options > 2 chars long), ifhp=... and -T options reprocessed
	 */

	pc_entry = getenv("PRINTCAP_ENTRY");
	/* check the environment variables */
	if( !ISNULL(pc_entry) ){
		DEBUG3("main: PRINTCAP_ENTRY '%s'", pc_entry );
		Split_STR_OBJ( /*p*/Printcap, /*nomod*/1, /*str*/pc_entry,
				/*type*/OBJ_T_HASH, /*linesep*/":", /*escape*/":",
				/*comment*/ 0, /*trim*/1, /*keysep*/"=",/*do_append*/',', /*lc*/1,
				/*flagvalues*/ 1, /*value_urlunesc*/0, MEMINFO );
		if(DEBUGL3)SHORT_DUMP_OBJ("main: printcap PRINTCAP_ENTRY", Printcap );
		/* now we add in the Printcap options */
		for( i = 0; i < LEN_HASH_OBJ(Printcap); ++i ){
			char *key = VAL_SHORTSTR_OBJ(KEY_HASH_OBJ(Printcap,i));
			char *value = VAL_STR_OBJ(VALUE_HASH_OBJ(Printcap,i));
			SET_HASH_STR_OBJ( Model, key, value, MEMINFO );
		}
		if(DEBUGL3)SHORT_DUMP_OBJ("main: PRINTCAP_ENTRY", Model );
		if( (s = GET_HASH_STR_OBJ(Model,"ifhp",MEMINFO)) ){
			Fix_special_user_opts("PRINTCAP ifhp Topts", Topts, Unsorted_Topts, s );
		}
	}
	if(DEBUGL3)SHORT_DUMP_OBJ("main: PRINTCAP_ENTRY", Model );


	/* get the argument lists */
	getargs( argc, argv );

	for( i = 0; i < LEN_HASH_OBJ(Topts); ++i ){
		char *key = VAL_SHORTSTR_OBJ(KEY_HASH_OBJ(Topts,i));
		char *value = VAL_STR_OBJ(VALUE_HASH_OBJ(Topts,i));
		SET_HASH_STR_OBJ( Model, key, value, MEMINFO );
	}

	if(DEBUGL3)SHORT_DUMP_OBJ("main: After Topts Model", Model );

	/* now figure out if there is an input file specified */
	/* we extract the debug information */
	if( (s = GET_HASH_STR_OBJ( Model, "trace", MEMINFO )) ){
		Trace_on_stderr = 1;
	}
	DEBUG1("main: Trace_on_stderr '%d'", Trace_on_stderr );
	if( (s = GET_HASH_STR_OBJ( Model, "debug", MEMINFO )) ){
		Parse_debug(s,1);
	}
	DEBUG1("main: Debug '%d', DbgFlag 0x%x, inputfile '%s'",
		Debug, DbgFlag, InputFile );

	/* check for config file */
	dump = GET_HASH_STR_OBJ(Model,"dump",MEMINFO);
	Config_file = 0;
	if( ISNULL(Config_file) ) Config_file = GET_HASH_STR_OBJ(Model,"config",MEMINFO);
	if( ISNULL(Config_file) ) Config_file = IFHP_CONF;

	/* find the model id. First we check the Toptions, then
	 * we check to see if we use a command line flag
	 */
	/* check for config file */
	Model_id = 0;
	if( ISNULL(Model_id) ) Model_id = GET_HASH_STR_OBJ(Model,"model",MEMINFO);

	DEBUG1("main: dump %s",dump );
	if( !Model_id &&
		(s = GET_HASH_STR_OBJ(Topts,"model_from_option",MEMINFO)) ){
		DEBUG1("main: checking model_from_option '%s'", s );
		for( ; !Model_id && *s; ++s ){
			c = cval(s);
			if( isupper(c) ) Model_id = Upperopts[c-'A'];
			if( islower(c) ) Model_id = Loweropts[c-'a'];
		}
	}
	DEBUG1("main: Model_id '%s'", Model_id );
	DEBUG1("main: LANG '%s', 'TRANSLATE TEST' is '%s'",
		getenv("LANG"), _("TRANSLATE TEST"));

	/* read the configuration file(s) */
	if( Read_file_list_OBJ( /*output*/Raw,
		/*filenames*/Config_file, /*directories*/0, /*required*/1,
		/*doinclude*/1, /*depth*/0, /*maxdepth*/4,
		/*type*/OBJ_T_LIST, /*linesep*/"\n", /*escape*/"\n",
		/*comment*/ '#', /*trim*/0, /*keysep*/"=",/*do_append*/',', /*lc*/1,
		/*flagvalues*/ 1, /*value_urlunesc*/0, errmsg, sizeof(errmsg), MEMINFO )
		< 0 ){
		Errorcode = JABORT;
		FATAL(LOGINFO)_("main: '%s'"), errmsg );
	}
	/* no configuration? big problems */
	if( LEN_LIST_OBJ(Raw) == 0 ){
		Errorcode = JABORT;
		FATAL(LOGINFO)_("main: no config file information in '%s'"), Config_file );
	}
	DEBUGFC(DDB2)SHORT_DUMP_OBJ("main: Raw", Raw );

	/* now we get the default values */
	DEBUG1("main: scanning Raw for default, then model '%s'", Model_id );
	Make_model_index( Raw, Index, Entries );
	Select_model_info( Model, Index, Entries, "default", 0, 3 );
	DEBUGFC(DDB1)SHORT_DUMP_OBJ("main: Model after defaults", Model );

	if( Model_id && cval(Model_id) && safestrcasecmp(Model_id, "default") ){
		DEBUG1("main: scanning for model '%s'", Model_id  );
		Select_model_info( Model, Index, Entries, Model_id, 0, 3 );
		DEBUGFC(DDB2)SHORT_DUMP_OBJ("main: Model", Model );
	}

	/* now we add in the Printcap options, overriding defaults AND printer entry */
	/* we include only names > 2 chars long to avoid conflicts */
	for( i = 0; i < Printcap->info.array.len; ++i ){
		char *key = VAL_SHORTSTR_OBJ(KEY_HASH_OBJ(Printcap,i));
		char *value = VAL_STR_OBJ(VALUE_HASH_OBJ(Printcap,i));
		if( safestrlen(key) > 2 ){
			SET_HASH_STR_OBJ( Model, key, value, MEMINFO );
		}
	}

	/* now we add in the Topts, overriding defaults, printer entry, printcap */
	for( i = 0; i < LEN_HASH_OBJ(Topts); ++i ){
		char *key = VAL_SHORTSTR_OBJ(KEY_HASH_OBJ(Topts,i));
		char *value = VAL_STR_OBJ(VALUE_HASH_OBJ(Topts,i));
		SET_HASH_STR_OBJ( Model, key, value, MEMINFO );
	}
	DEBUGFC(DDB2)SHORT_DUMP_OBJ("main: Model after Topts", Model );

	/* handy way to get a dump of the configuration for the printer */
	if( dump ){
		FPRINTF(STDOUT, "Configuration for model='%s'",
			Model_id?Model_id:"default");
		for( i = 0; i < LEN_LIST_OBJ(Model); ++i ){
			s = GET_ENTRY_LIST_OBJ(Model,i);
			FPRINTF(STDOUT, "[%3d]  %s\n", i, s );
		}
		exit(0);
	}

	/* if we are in OF mode, get the of_options value */
	if( OF_Mode && (s = GET_HASH_STR_OBJ( Model,"of_options", MEMINFO )) ){ 
		Split_STR_OBJ( /*p*/Model, /*nomod*/1, /*str*/s,
				/*type*/OBJ_T_HASH, /*linesep*/LISTSEP, /*escape*/LISTSEP,
				/*comment*/ 0, /*trim*/1, /*keysep*/"=",/*do_append*/',', /*lc*/1,
				/*flagvalues*/ 1, /*value_urlunesc*/0, MEMINFO );
	}

	/* set the named parameter variables with values */
	Initialize_parms(Model, Valuelist );
	if(DEBUGL2) Dump_parms( "main: variable values", Valuelist );

	/* check for -Zlogall allowed, and set it */
	if( (s = GET_HASH_STR_OBJ( Model, "user_opts", MEMINFO)) ){
		Split_STR_OBJ( /*p*/User_opts, /*nomod*/1, /*str*/s,
				/*type*/OBJ_T_HASH, /*linesep*/LISTSEP, /*escape*/LISTSEP,
				/*comment*/ 0, /*trim*/1, /*keysep*/"=",/*do_append*/',', /*lc*/1,
				/*flagvalues*/ 1, /*value_urlunesc*/0, MEMINFO );
		if(DEBUGL4)SHORT_DUMP_OBJ("main: user_opts", User_opts);
		if( GET_HASH_IVAL_OBJ( Zopts, "logall" )
			&& GET_HASH_STR_OBJ( User_opts, "logall", MEMINFO ) ){
			Logall = 1;
		}
	}

	/* clean up statusfile information */
	if( Status_fd > 0 ){
		Errorcode = JABORT;
		FATAL(LOGINFO)_("main: you opened the statusfile too early!!!"));
	}
	if( (s = Loweropts['s'-'a']) ) Statusfile = s;

	LOGMSG(LOG_INFO)_("main: using model '%s'"), Model_id?Model_id:_("DEFAULT"));

	/* if we have version request, print out the version */
	if( Debug || GET_HASH_IVAL_OBJ(Topts,"version")
		|| GET_HASH_IVAL_OBJ(Zopts,"version") ){
		Write_fd_str(2,_id);
	}
	/* handle specifying foomatic in the printcap */

	if(DEBUGL2)SHORT_DUMP_OBJ( "main: Model information", Model );
	if(DEBUGL3)SHORT_DUMP_OBJ("Zopts",Zopts);
	if(DEBUGL3)SHORT_DUMP_OBJ("Topts",Topts);

	(void)signal(SIGPIPE, SIG_IGN);
	(void)signal(SIGCHLD, SIG_DFL);

	/* initializes connection */
	if( Appsocket ){
		if( ISNULL(Device) ){
			Device = GET_HASH_STR_OBJ(Model, "lp", MEMINFO );
			if( !(s = safestrchr(Device,'%')) ){
				Errorcode = JABORT;
				FATAL(LOG_INFO)_("main: Appsocket and bad printcap :lp='%s'"), Device );
			}
		}
		DEBUG1("main: appsocket device now '%s'", Device );
	}
	if( !ISNULL(Device) ){
		/* this is brutal, and will force a socket close */
#ifndef SHUT_RDRW
#define SHUT_RDRW 2
#endif
		shutdown(1,SHUT_RDRW);
		/* now we do the close */
		close(1);
		Open_device( Device );
	}
	if( Status ){
		/* if we have an SNMP monitor program then we
		 * start it up.  We make its FD 0 and FD 1 the
		 * end of a bidirectional pipe
		 */
		if( Snmp_monitor ){
			OBJ *l = 0;
			char **argv;
			int in[2], pid;
			l = NEW_OBJ(l,MEMINFO);
#if !defined(HAVE_SOCKETPAIR)
			Errorcode = ABORT;
			FATAL(LOGINFO)_("ifhp: requires socketpair() system call for snmp monitoring"));
#else
			if( socketpair( AF_UNIX, SOCK_STREAM, 0, in ) == -1 ){
				Errorcode = JFAIL;
				LOGERR_DIE(LOG_INFO)_("ifhp: socketpair() failed"));
			}
#endif
			if( ISNULL(Snmp_sync_status) ){
				Errorcode = JABORT;
				FATAL(LOGINFO) _("ifhp: missing snmp_sync_status value") );
			}
			if( ISNULL(Snmp_end_status) ){
				Errorcode = JABORT;
				FATAL(LOGINFO) _("ifhp: missing snmp_end_status value") );
			}
			if( ISNULL(Snmp_program) ){
				Errorcode = JABORT;
				FATAL(LOGINFO) _("ifhp: missing snmp_monitor value") );
			}
			DEBUG1("main: start snmp_dev %s, Device %s, :lp=%s",
				Snmp_dev, Device, GET_HASH_STR_OBJ(Model, "lp", MEMINFO ) );
			if( ISNULL(Snmp_dev) ) Snmp_dev = Device;
			if( ISNULL(Snmp_dev) ) Snmp_dev = GET_HASH_STR_OBJ(Model, "lp", MEMINFO );
			if( (s = safestrchr(Snmp_dev,'%')) ) *s = 0;
			LOGMSG(LOG_INFO)"snmp device '%s'", Snmp_dev );
			if( ISNULL(Snmp_dev) ){
				Errorcode = JABORT;
				FATAL(LOGINFO) _("ifhp: missing snmp_dev device"));
			}
			DEBUG1("main: snmp_monitor device now '%s'", Snmp_dev );
			SET_HASH_STR_OBJ( Model,"snmp_dev",Snmp_dev,MEMINFO);
			s = Fix_option_str( Snmp_program, 0, 1, 0 );
			DEBUG1("using snmp_program '%s'", s );
			Split_cmd_line_OBJ( l, s );
			argv = GET_LIST_OBJ(l);
			if( stat(argv[0],&statb) ){
				Errorcode = JABORT;
				LOGERR_DIE(LOGINFO)_("ifhp: missing snmp_program '%s'"), Snmp_program );
			}
			/* now we exec the filter process */
			if( (pid = fork()) == -1 ){
				Errorcode = JABORT;
				LOGERR_DIE(LOGINFO)_("ifhp: fork failed"));
			} else if( pid == 0 ){
				char **argv = GET_LIST_OBJ(l);
				char buffer[64];
				dup2(in[1], 0);
				dup2(in[1], 1);
				close_on_exec(3);
				execve( argv[0], argv, Envp );
				/* ooops... error */
				SNPRINTF(buffer,sizeof(buffer)) _("execv '%s' failed - %s"), argv[0],
					Errormsg(errno) );
				Write_fd_str(2,buffer);
				exit(JABORT);
			}
			SAFEFREE(s);
			FREE_OBJ(l); l = 0;
			close(in[1]);
			Snmp_fd = in[0];
			Set_max_fd(Snmp_fd);
			DEBUG4("ifhp: Snmp_fd '%d'", Snmp_fd );
		} else {
			Status = Fd_readable(1, &Poll_for_status);
		}
	} 
	if( Poll_for_status ){
		/* handles the status case with the timeout */
		if( Dev_sleep <= 0 ) Dev_sleep = 100;
	}
	DEBUG1("main: poll for status %d, Dev_sleep %d msec",
		Poll_for_status, Dev_sleep);
	Set_block_io( 0 );
	Set_block_io( 1 );

	/* set the keepalive options */
	Set_keepalive(1);

	/* get the various options */
	if( (s = GET_HASH_STR_OBJ( Model, "pjl_only", MEMINFO)) ){
		lowercase(s);
		Split_STR_OBJ( /*p*/Pjl_only, /*nomod*/1, /*str*/s,
				/*type*/OBJ_T_HASH, /*linesep*/WHITESPACE, /*escape*/LISTSEP WHITESPACE,
				/*comment*/ 0, /*trim*/1, /*keysep*/"=",/*do_append*/',', /*lc*/1,
				/*flagvalues*/ 1, /*value_urlunesc*/0, MEMINFO );
		if(DEBUGL4)SHORT_DUMP_OBJ("main: Pjl_only", Pjl_only);
	}
	if( (s = GET_HASH_STR_OBJ( Model, "pjl_except", MEMINFO)) ){
		lowercase(s);
		Split_STR_OBJ( /*p*/Pjl_except, /*nomod*/1, /*str*/s,
				/*type*/OBJ_T_HASH, /*linesep*/WHITESPACE, /*escape*/LISTSEP WHITESPACE,
				/*comment*/ 0, /*trim*/1, /*keysep*/"=",/*do_append*/',', /*lc*/1,
				/*flagvalues*/ 1, /*value_urlunesc*/0, MEMINFO );
		if(DEBUGL4)SHORT_DUMP_OBJ("main: Pjl_except", Pjl_except);
	}
	if( (s = GET_HASH_STR_OBJ( Model, "pjl_options_set", MEMINFO)) ){
		lowercase(s);
		Split_STR_OBJ( /*p*/Pjl_options_set, /*nomod*/1, /*str*/s,
				/*type*/OBJ_T_HASH, /*linesep*/WHITESPACE, /*escape*/LISTSEP WHITESPACE,
				/*comment*/ 0, /*trim*/1, /*keysep*/"=",/*do_append*/',', /*lc*/1,
				/*flagvalues*/ 1, /*value_urlunesc*/0, MEMINFO );
		if(DEBUGL4)SHORT_DUMP_OBJ("main: Pjl_options_set", Pjl_options_set);
	}
	if( (s = GET_HASH_STR_OBJ( Model, "pjl_vars_set", MEMINFO)) ){
		lowercase(s);
		DEBUG4("main: pjl_vars_set '%s'", s );
		if(DEBUGL4)SHORT_DUMP_OBJ("main: Pjl_vars_set BEFORE", Pjl_vars_set);
		Split_STR_OBJ( /*p*/Pjl_vars_set, /*nomod*/1, /*str*/s,
				/*type*/OBJ_T_HASH, /*linesep*/WHITESPACE, /*escape*/LISTSEP WHITESPACE,
				/*comment*/ 0, /*trim*/1, /*keysep*/"=",/*do_append*/',', /*lc*/1,
				/*flagvalues*/ 1, /*value_urlunesc*/0, MEMINFO );
		if(DEBUGL4)SHORT_DUMP_OBJ("main: Pjl_vars_set", Pjl_vars_set);
	}
	if( (s = GET_HASH_STR_OBJ( Model, "pjl_vars_except", MEMINFO)) ){
		lowercase(s);
		Split_STR_OBJ( /*p*/Pjl_vars_except, /*nomod*/1, /*str*/s,
				/*type*/OBJ_T_HASH, /*linesep*/WHITESPACE, /*escape*/LISTSEP WHITESPACE,
				/*comment*/ 0, /*trim*/1, /*keysep*/"=",/*do_append*/',', /*lc*/1,
				/*flagvalues*/ 1, /*value_urlunesc*/0, MEMINFO );
		if(DEBUGL4)SHORT_DUMP_OBJ("main: Pjl_vars_except", Pjl_vars_except);
	}
	if( (s = GET_HASH_STR_OBJ( Model, "pjl_user_opts", MEMINFO)) ){
		lowercase(s);
		Split_STR_OBJ( /*p*/Pjl_user_opts, /*nomod*/1, /*str*/s,
				/*type*/OBJ_T_HASH, /*linesep*/WHITESPACE, /*escape*/LISTSEP WHITESPACE,
				/*comment*/ 0, /*trim*/1, /*keysep*/"=",/*do_append*/',', /*lc*/1,
				/*flagvalues*/ 1, /*value_urlunesc*/0, MEMINFO );
		if(DEBUGL4)SHORT_DUMP_OBJ("main: Pjl_user_opts", Pjl_user_opts);
	}
	if( (s = GET_HASH_STR_OBJ( Model, "pcl_user_opts", MEMINFO)) ){
		lowercase(s);
		Split_STR_OBJ( /*p*/Pcl_user_opts, /*nomod*/1, /*str*/s,
				/*type*/OBJ_T_HASH, /*linesep*/WHITESPACE, /*escape*/LISTSEP WHITESPACE,
				/*comment*/ 0, /*trim*/1, /*keysep*/"=",/*do_append*/',', /*lc*/1,
				/*flagvalues*/ 1, /*value_urlunesc*/0, MEMINFO );
		if(DEBUGL4)SHORT_DUMP_OBJ("main: Pcl_user_opts", Pcl_user_opts);
	}
	if( (s = GET_HASH_STR_OBJ( Model, "ps_user_opts", MEMINFO)) ){
		lowercase(s);
		Split_STR_OBJ( /*p*/Ps_user_opts, /*nomod*/1, /*str*/s,
				/*type*/OBJ_T_HASH, /*linesep*/WHITESPACE, /*escape*/LISTSEP WHITESPACE,
				/*comment*/ 0, /*trim*/1, /*keysep*/"=",/*do_append*/',', /*lc*/1,
				/*flagvalues*/ 1, /*value_urlunesc*/0, MEMINFO );
		if(DEBUGL4)SHORT_DUMP_OBJ("main: Ps_user_opts", Ps_user_opts);
	}
	if( (s = GET_HASH_STR_OBJ( Model, "pjl_error_codes", MEMINFO)) ){
		Split_STR_OBJ( /*p*/Pjl_error_codes, /*nomod*/1, /*str*/s,
				/*type*/OBJ_T_HASH, /*linesep*/"\n", /*escape*/"\n",
				/*comment*/ 0, /*trim*/1, /*keysep*/"=",/*do_append*/',', /*lc*/1,
				/*flagvalues*/ 1, /*value_urlunesc*/0, MEMINFO );
		if(DEBUGL4)SHORT_DUMP_OBJ("main: Pjl_error_codes", Pjl_error_codes);
	}
	if( (s = GET_HASH_STR_OBJ( Model, "pjl_alert_codes", MEMINFO)) ){
		Split_STR_OBJ( /*p*/Pjl_alert_codes, /*nomod*/1, /*str*/s,
				/*type*/OBJ_T_LIST, /*linesep*/WHITESPACE, /*escape*/LISTSEP WHITESPACE,
				/*comment*/ 0, /*trim*/1, /*keysep*/"=",/*do_append*/',', /*lc*/1,
				/*flagvalues*/ 1, /*value_urlunesc*/0, MEMINFO );
		if(DEBUGL4)SHORT_DUMP_OBJ("main: Pjl_alert_codes", Pjl_alert_codes);
	}
	if( (s = GET_HASH_STR_OBJ( Model, "pjl_quiet_codes", MEMINFO)) ){
		Split_STR_OBJ( /*p*/Pjl_quiet_codes, /*nomod*/1, /*str*/s,
				/*type*/OBJ_T_LIST, /*linesep*/WHITESPACE, /*escape*/LISTSEP WHITESPACE,
				/*comment*/ 0, /*trim*/1, /*keysep*/"=",/*do_append*/',', /*lc*/1,
				/*flagvalues*/ 1, /*value_urlunesc*/0, MEMINFO );
		if(DEBUGL4)SHORT_DUMP_OBJ("main: Pjl_quiet_codes", Pjl_quiet_codes);
	}
	if( (s = GET_HASH_STR_OBJ( Model, "status_ignore", MEMINFO)) ){
		Split_STR_OBJ( /*p*/Ignore, /*nomod*/1, /*str*/s,
				/*type*/OBJ_T_LIST, /*linesep*/WHITESPACE, /*escape*/LISTSEP WHITESPACE,
				/*comment*/ 0, /*trim*/1, /*keysep*/"=",/*do_append*/',', /*lc*/1,
				/*flagvalues*/ 1, /*value_urlunesc*/0, MEMINFO );
		if(DEBUGL4)SHORT_DUMP_OBJ("main: Status_ignore", Ignore);
	}
	if( (s = GET_HASH_STR_OBJ( Model, "status_translate", MEMINFO)) ){
		Split_STR_OBJ( /*p*/Translate, /*nomod*/1, /*str*/s,
				/*type*/OBJ_T_LIST, /*linesep*/WHITESPACE, /*escape*/LISTSEP WHITESPACE,
				/*comment*/ 0, /*trim*/1, /*keysep*/"=",/*do_append*/',', /*lc*/1,
				/*flagvalues*/ 1, /*value_urlunesc*/0, MEMINFO );
		if(DEBUGL4)SHORT_DUMP_OBJ("main: Status_translate", Translate);
	}
	if( (s = GET_HASH_STR_OBJ( Model, "snmp_status_fields", MEMINFO)) ){
		Split_STR_OBJ( /*p*/Status_fields, /*nomod*/1, /*str*/s,
				/*type*/OBJ_T_LIST, /*linesep*/WHITESPACE, /*escape*/LISTSEP WHITESPACE,
				/*comment*/ 0, /*trim*/1, /*keysep*/"=",/*do_append*/',', /*lc*/1,
				/*flagvalues*/ 1, /*value_urlunesc*/0, MEMINFO );
		if(DEBUGL4)SHORT_DUMP_OBJ("main: Snmp_status_fields", Status_fields);
	}


	do_pagecount = Check_pagecount(&pagecount_ps, &pagecount_pjl, &pagecount_prog, &pagecount_snmp );
	/* now the real work */

	Process_job(do_pagecount, pagecount_ps, pagecount_pjl, pagecount_prog, pagecount_snmp );
	
	/* cleanup and exit */
	Errorcode = 0;
	Set_block_io(1);
	cleanup(0);
	return(0);
}

void cleanup(int sig)
{
	if( Errorcode < 0 ) Errorcode = -Errorcode;
    DEBUG3("cleanup: Signal '%s', Errorcode %d", Sigstr(sig), Errorcode );
	Set_block_io( 1 );
	close(0);
	close(1);
	close(2);
    exit(Errorcode);
}

 char *msg[] = {
 "usage: %s [-c] [-X arg]* [-Xarg] [acctfile]",
 " -c      - the 'no formatting' legacy option for lpd filters",
 " -X arg",
 " -Xarg   - options + arguments, all letters but Z and T, see LPRng documentation",
 " -Targ   - special flags, set by administrator",
 " -Zarg   - special flags, passed by user through to filter",
 " -=      - special case, causes usage message",
 0
};

void usage()
{
	int i;
	for( i = 0; msg[i]; ++i ){
		if( i ){
			FPRINTF(STDERR, "%s\n", msg[i] );
		} else {
			FPRINTF(STDERR, msg[i], Name );
			FPRINTF(STDERR, "\n");
		}
	}
	Parse_debug("=",-1);
	FPRINTF(STDERR,"%s\n", _id );
	exit(1);
}
/*
 * getargs( int argc, char **argv )
 *  - get the options from the argument list
 */

void getargs( int argc, char **argv )
{
	int i, flag;
	char *arg, *s, m[2];

	/* LOGMSG(LOG_INFO)"testing"); */
	DEBUG3("getargs: starting, debug %d", Debug);
	for( i = 1; i < argc; ++i ){
		arg = argv[i];
		if( *arg++ != '-' ) break;
		flag = *arg++;
		if( flag == '-' ){
			if( !cval(arg) ){
				++i;
				break;
			}
			DEBUG3("getargs: option --'%s'", arg);
			/* we have --key=value */
			Fix_special_user_opts("Topts", Topts, Unsorted_Topts, arg );
			continue;
		}
		if( flag == '=' ){
			usage();
			exit(1);
		}
		if( flag == 'c' ){
			 arg = "1";
		}
		if( *arg == 0 ){
			if( i < argc ){
				arg = argv[++i];
			} else {
				FATAL(LOGINFO) "missing argument for flag '%c'", flag );
			}
		}
		/* we duplicate the strings */
		DEBUG3("getargs: flag '%c'='%s'", flag, arg);
		if( islower(flag) ){
			Loweropts[flag-'a'] = arg;
		} else if( isupper(flag) ){
			switch( flag ){
			case 'T': case 'Z':
				if( (s = Upperopts[flag-'A']) ){
					Upperopts[flag-'A'] = 
						safestrdup3(s,",",arg,MEMINFO);
					if( s ) SAFEFREE(s);
				} else {
					Upperopts[flag-'A'] = safestrdup(arg,MEMINFO);
				}
				break;
			default:
				Upperopts[flag-'A'] = arg;
				break;
			}
		}
	}
	if( i < argc ){
		Accountfile = argv[i];
	} else if( (s = Loweropts['a'-'a']) ){
		Accountfile = s;
	}
	DEBUG1("getargs: finished options");
	/* set the Topts and Zopts values */
	memset(m,sizeof(m),0);
	m[0] = m[1] = 0;
	for( i = 'a'; i <= 'z'; ++i ){
		m[0] = i;
		if( (s = Loweropts[i-'a']) ) SET_HASH_STR_OBJ(Topts,m,s,MEMINFO);
	}
	for( i = 'A'; i <= 'Z'; ++i ){
		m[0] = i;
		if( (s = Upperopts[i-'A']) ) SET_HASH_STR_OBJ(Topts,m,s,MEMINFO);
	}
	if( (s = Upperopts['F'-'A']) && cval(s) == 'o' ){
		OF_Mode = 1;
	}
	Fix_special_user_opts("Topts", Topts, Unsorted_Topts, Upperopts['T'-'A'] );
	Fix_special_user_opts("Zopts", Zopts, Unsorted_Zopts, Upperopts['Z'-'A'] );
}

void Fix_special_user_opts( char *name, OBJ *opts, OBJ *unsorted_opts,
	 char *line )
{
	OBJ *l = 0, *v = 0;
	int i;
	char *s, *key, *value;

	DEBUG1("Fix_special_user_opts: %s - '%s'", name, line );
	l = NEW_OBJ(l,MEMINFO);
	v = NEW_OBJ(v,MEMINFO);
	Split_STR_OBJ( /*p*/l, /*nomod*/1, /*str*/line,
			/*type*/OBJ_T_LIST, /*linesep*/",", /*escape*/",",
			/*comment*/ 0, /*trim*/1, /*keysep*/"=",/*do_append*/',', /*lc*/1,
			/*flagvalues*/ 1, /*value_urlunesc*/0, MEMINFO );
	for( i = 0; i < LEN_LIST_OBJ(l); ++i ){
		s = GET_ENTRY_LIST_OBJ(l,i);
		Clear_OBJ(v);
		Split_STR_OBJ( /*p*/v, /*nomod*/1, /*str*/s,
			/*type*/OBJ_T_HASH, /*linesep*/0, /*escape*/0,
			/*comment*/ 0, /*trim*/1, /*keysep*/"=",/*do_append*/',', /*lc*/1,
			/*flagvalues*/ 1, /*value_urlunesc*/0, MEMINFO );
		key = VAL_SHORTSTR_OBJ(KEY_HASH_OBJ(v,0));
		value = VAL_STR_OBJ(VALUE_HASH_OBJ(v,0));
		DEBUG1("Fix_special_user_opts: option '%s' = '%s'", key, value );
		if( strcasecmp( key, "font") ){
			OBJ *p = GET_HASH_OBJ(opts,key);
			if( !p ){
				SET_HASH_STR_OBJ(opts,key,value,MEMINFO);
			} else {
				APPEND_STR_OBJ(MEMINFO,p,",",value,0);
			}
		} else {
			SET_HASH_STR_OBJ(opts,key,value,MEMINFO);
		}
		APPEND_LIST_OBJ(unsorted_opts,key,MEMINFO);
	}
	FREE_OBJ(v); v = 0;
	FREE_OBJ(l); l = 0;
	if(DEBUGL1){
		char msg[128];
		SNPRINTF(msg, sizeof(msg))  "Fix_special_user_opts - '%s' sorted", name );
		SHORT_DUMP_OBJ(msg, opts );
		SNPRINTF(msg, sizeof(msg))  "Fix_special_user_opts - '%s' unsorted", name );
		SHORT_DUMP_OBJ(msg, unsorted_opts );
	}
}

/*
 * Output buffer management
 *  Set up and put values into an output buffer for
 *  transmission at a later time
 */
void Init_outbuf()
{
	DEBUG4("Init_outbuf: Outbuf 0x%lx, Outmax %d, Outlen %d",
		(long)Outbuf, Outmax, Outlen );
	if( Outmax <= 0 ) Outmax = OUTBUFFER;
	if( Outbuf == 0 ) Outbuf = realloc_or_die( Outbuf, Outmax+1,MEMINFO);
	Outlen = 0;
	Outbuf[0] = 0;
}

void Put_outbuf_str( char *s )
{
	if( s && *s ) Put_outbuf_len( s, safestrlen(s) );
}

void Put_outbuf_len( char *s, int len )
{
	DEBUG4("Put_outbuf_len: starting- Outbuf 0x%lx, Outmax %d, Outlen %d, len %d",
		(long)Outbuf, Outmax, Outlen, len );
	if( s == 0 || len <= 0 ) return;
	if( Outmax - Outlen <= len ){
		Outmax += ((OUTBUFFER + 1023 + len)/1024)*1024;
		Outbuf = realloc_or_die( Outbuf, Outmax+1,MEMINFO);
		DEBUG4("Put_outbuf_len: update- Outbuf 0x%lx, Outmax %d, Outlen %d, len %d",
		(long)Outbuf, Outmax, Outlen, len );
	}
	memmove( Outbuf+Outlen, s, len );
	Outlen += len;
	Outbuf[Outlen] = 0;
}

/*
 * Input buffer management
 *  Set up and put values into an input buffer for
 *  scanning purposes
 */
void Init_inbuf()
{
	Inmax = LARGEBUFFER;
	Inbuf = realloc_or_die( Inbuf, Inmax+1,MEMINFO);
	Inbuf[Inmax] = 0;
	Inlen = 0;
	Inbuf[0] = 0;
}

void Put_inbuf_len( char *str, int len )
{
	DEBUG4("Put_inbuf_len: before buffer '%s', adding '%s', len %d", Inbuf, str, len );
	if( Inbuf == 0 || Inmax - Inlen <= (len+1) ){
		Inmax += ((LARGEBUFFER + len+1)/1024)*1024;
		Inbuf = realloc_or_die( Inbuf, Inmax+1,MEMINFO);
		if( !Inbuf ){
			Errorcode = JABORT;
			LOGERR_DIE(LOGINFO) _("Put_outbuf_len: realloc %d failed"), len );
		}
	}
	memmove( Inbuf+Inlen, str, len+1 );
	Inlen += len;
	Inbuf[Inlen] = 0;
	DEBUG4("Put_inbuf_len: buffer '%s'", Inbuf );
}

/*
 * void Get_inbuf_str()
 *  get a line from the input buffer and parse it for printer
 *  status
 */
 static char *PS_ctrl = "\004\014\024\034";
void Get_inbuf_str(void)
{
	int c;
	char *s, *t, *u, *v;
	/* DEBUG4("Get_inbuf_str: buffer '%s'", Inbuf); */
	Inbuf[Inlen] = 0;
	/* remove \r */
	for( s = Inbuf; (s = safestrchr( s, '\r' )); ){
		memmove( s, s+1, safestrlen(s)+1 );
	}
	for( s = Inbuf; (t = safestrpbrk( s, LINEENDS )); s = t ){
		*t++ = 0;
		DEBUG4("Get_inbuf_str: found '%s'", s);
		/* check to see if you have a %%[ ... ]%% */
		while(  (v = strstr(s,"%%[")) && (u = strstr(v,"]%%")) ){
			u += 3;
			c = cval(u);
			*u = 0;
			Pr_status( v );
			*u = c;
			s = u;
		}
		/* now we treat ^D and ^T like NL */
		while( (u = safestrpbrk(s,PS_ctrl)) ){
			*u++ = 0;
			Pr_status( s );
			s = u;
		}
		if( !ISNULL(s) ){
			Pr_status( s );
		}
	}
	/* check to see if you have a %%[ ... ]%% */
	while(  (v = strstr(s,"%%[")) && (u = strstr(v,"]%%")) ){
		u += 3;
		c = cval(u);
		*u = 0;
		Pr_status( v );
		*u = c;
		s = u;
	}
	/* now we treat ^D and ^T like NL */
	while( (u = safestrpbrk(s,PS_ctrl)) ){
		*u++ = 0;
		Pr_status( s );
		s = u;
	}
	memmove( Inbuf, s, safestrlen(s)+1 );
	DEBUG4("Get_inbuf_str: final '%s'", Inbuf);
	Inlen = safestrlen(Inbuf);
}


/*
 * Printer Status Reporting
 * PJL:
 *  We extract info of the form:
 *  @PJL XXXX P1 P2 ...
 *  V1
 *  V2
 * we have
 *  PJL USTATUS JOB
 *    END/START (next line)
 *    KEY=VALUE (next lines)
 *  PJL USTATUS TIMED
 *    KEY=VALUE (next lines)
 *  PJL USTATUS PAGE
 *    pagecount  - some printers
 *    PAGES=VALUE - some printers
 *  PJL INFO ID
 *    printer information (next line)
 *     ID=xxxx   - some printers
 *     xxxxxx    - other printers
 *  PJL INFO PAGECOUNT
 *    pagecount information (next line)
 *     PAGECOUNT=xxxx   - some printers
 *     xxxxxx           - other printers
 *  PJL ECHO xxxxxx     - echo information
 *
 *  - we put this into the 'current status' array
 * PostScript:
 *  We extract info of the form:
 *  %%[ key: value value key: value value ]%%
 *    we then create strings
 *     key=value value
 *  - we put this into the 'current status' array
 *
 */

void Pr_status( char *str )
{
	char *s, *t;
	int c, i, pjlline, found;
	char *ps_str, *eq_line;
	OBJ *l = 0;
	static char *pjlvar;
	static int infovar;

	l = NEW_OBJ(l,MEMINFO);
	c = pjlline = found = 0;
	ps_str = eq_line = 0;
	DEBUG2("Pr_status: start str '%s', pjlvar '%s', infovar %d",
		str, pjlvar, infovar );
	/* if(DEBUGL4)SHORT_DUMP_OBJ("Pr_status - before Devstatus", Devstatus); */

	if( Logall && str ){
		LOGMSG(LOG_INFO) _("Pr_status: printer status '%s'"), str );
	}

	/* if the previous line was a PJL line,  then the
	 * last entry may be a variable name and the next
	 * line a value for it.  We need to append to the variable value
	 */

	trunc_str(str);
	while( isspace(cval(str)) ) ++str;
	pjlline = !safestrncasecmp( str, "@PJL",4);
	if( (ps_str = safestrstr( str,"%%[" )) ){
		ps_str += 3;
	} else {
		/* look for key=value */
		eq_line = safestrchr( str, '=' );
	}
	if( ISNULL(str) ) return;

	if( pjlline ){
		/* we have
		 *PJL USTATUS JOB
         *  END/START (next line)
		 *  KEY=VALUE (next lines)
		 *PJL USTATUS TIMED
		 *  KEY=VALUE (next lines)
		 *PJL USTATUS PAGE
		 *  pagecount  - some printers
		 *  PAGE=VALUE - some printers
		 *PJL INFO ID
		 *  printer information (next line)
		 *   ID=xxxx   - some printers
		 *   xxxxxx    - other printers
		 *PJL INFO PAGECOUNT
		 *  pagecount information (next line)
		 *   PAGECOUNT=xxxx   - some printers
		 *   xxxxxx           - other printers
		 *PJL ECHO xxxxxx     - echo information
		 */
		DEBUG4("Pr_status: doing PJL status on '%s'", str);
		if( pjlvar ) SAFEFREE(pjlvar); pjlvar = 0;
		infovar = 0;
		if( (s = safestrstr( str, " ECHO ") ) ){
			/* we have the echo value */
			SET_HASH_STR_OBJ(Devstatus,"echo",s+6,MEMINFO);
			DEBUG4("Pr_status: found echo '%s'", s+6 );
		} else {
			int n;
			char *s;
			/* get the last entry on the line - name of PJL variable */
			Split_STR_OBJ( /*p*/l, /*nomod*/1, /*str*/str,
			/*type*/OBJ_T_LIST, /*linesep*/WHITESPACE, /*escape*/WHITESPACE,
			/*comment*/ 0, /*trim*/1, /*keysep*/"=",/*do_append*/',', /*lc*/1,
			/*flagvalues*/ 1, /*value_urlunesc*/0, MEMINFO );
			n = LEN_LIST_OBJ(l);
			if( n > 2 ){
				/* get the last keyword */
				s = GET_ENTRY_LIST_OBJ(l,n-1);
				pjlvar = safestrdup2(s,"=",MEMINFO);
				s = GET_ENTRY_LIST_OBJ(l,n-2);
				infovar = !safestrcasecmp(s,"INFO");
			}
			DEBUG4("Pr_status: PJL var '%s', infovar %d", pjlvar, infovar );
			Clear_OBJ(l);
		}
	} else if( eq_line ){
		/* now we check for xx=value entries */
		Check_device_status(str, infovar );
		if( pjlvar ) SAFEFREE(pjlvar); pjlvar = 0;
		infovar = 0;
	} else if( ps_str ){
		int found_error = 0;
		char value[SMALLBUFFER];
		if( pjlvar ) SAFEFREE(pjlvar); pjlvar = 0;
		/* we do Postscript status */
		if( !ps_str ) ps_str = str;
		if( (s = safestrstr(ps_str, "]%%")) ) *s = 0;
		Split_STR_OBJ( /*p*/l, /*nomod*/1, /*str*/ps_str,
			/*type*/OBJ_T_LIST, /*linesep*/WHITESPACE, /*escape*/WHITESPACE,
			/*comment*/ 0, /*trim*/1, /*keysep*/"=",/*do_append*/',', /*lc*/1,
			/*flagvalues*/ 1, /*value_urlunesc*/0, MEMINFO );
		DEBUG4("Pr_status: PostScript info '%s'", ps_str );
		if(DEBUGL4)SHORT_DUMP_OBJ("Pr_status: PostScript", l);
		value[0] = 0;
		for( i = 0; i < LEN_LIST_OBJ(l); ++i ){
			s = GET_ENTRY_LIST_OBJ(l,i);
			DEBUG4("Pr_status: list [%d]='%s', value '%s'", i, s, value );
			if( (t = safestrchr(s,':')) && t != s ){
				DEBUG4("Pr_status: found ps status '%s'",value );
				if( value[0] ){
					int n = strlen(value);
					if( cval(value+n-1) == ';' ){
						value[n-1] = 0;
					}
					Check_device_status(value, 0);
				}
				*t++ = 0;
				if( !found_error ) found_error = !safestrcasecmp(s,"error");
				safestrncpy(value,s);
				safestrncat(value,"=");
				safestrncat(value,t);
			} else if( value[0] ){
				int n = safestrlen(value);
				if( cval(value+n-1) != '=' ){
					safestrncat( value, " ");
				}
				safestrncat( value, s);
			}
		}
		if( value[0] ){
			int n = strlen(value);
			if( cval(value+n-1) == ';' ){
				value[n-1] = 0;
			}
			DEBUG4("Pr_status: found ps status '%s'",value );
			Check_device_status(value, 0);
		}
		if( found_error ){
			Errorcode = JFAIL;
			FATAL(LOG_INFO)"Pr_status: %s", ps_str );
		}
		Clear_OBJ(l);
	} else if( pjlvar ){
		/* from the previous line */
		int n = safestrlen(pjlvar);
		if( cval(pjlvar+n-1) != '=' ){
			pjlvar = safeextend3(pjlvar,"\n",str, MEMINFO);
		} else {
			pjlvar = safeextend2(pjlvar,str, MEMINFO);
		}
		DEBUG4("Pr_status: pjlvar '%s'", pjlvar );
		Check_device_status(pjlvar, infovar );
	}
	/* if(DEBUGL4)SHORT_DUMP_OBJ("Pr_status - Devstatus", Devstatus); */
	FREE_OBJ(l); l = 0;
}

/*
 * Check_device_status( char *line, int infovar )
 *  - the line has to have the form DEVICE="nnn"
 *  - if infovar != 0, then previous lines was PJL INFO 
 */

void Check_device_status( char *line, int infovar )
{
	char *old, *value, *handler = 0, *s;
	int i, c, alert, fd, found, n, ignore = 0;
	char fixed_value[SMALLBUFFER];

	DEBUG2("Check_device_status: '%s'", line );
	lowercase(line);
	if( !(value = safestrchr( line, '=')) ){
		return;
	}
	/* remove trailing spaces and leading and trailing quotes  */
	{
		s = value;
		while( s > line && isspace(cval(s-1)) ) --s;
		*s = 0;
		if( ISNULL(line) ) return;
	}
	*value++ = 0;
	/* turn blanks to _ */
	{
		s = line;
		for( s = line; (c = cval(s)); ++s ){
			if( isspace(c) ) *s = '_';
		}
	}
	/* check for translation */
	found = 0;
	for( i = 0; !found && i < LEN_LIST_OBJ(Translate); ++i ){
		char *t;
		s = GET_ENTRY_LIST_OBJ(Translate,i);
		t = safestrchr(s,'=');
		DEBUG2("Check_device_status: test translate '%s' to '%s'", line, s );
		if( t ){
			*t = 0;
			if( (found = !Globmatch(s,line )) ){
				line = t+1;
			}
			*t = '=';
		}
	}
	DEBUG2("Check_device_status: we have key '%s'", line );
	/* now we see if it is to be ignored */
	for( i = 0; i < LEN_LIST_OBJ(Ignore); ++i ){
		s = GET_ENTRY_LIST_OBJ(Ignore,i);
		if( !Globmatch( GET_ENTRY_LIST_OBJ(Ignore,i), line ) ){
			DEBUG2("Check_device_status: ignoring '%s'", line );
			ignore = 1;
			break;
		} 
	}
	/* remove leading spaces from status */
	while( isspace(cval(value)) ) ++value;
	if( cval(value) == '"' ){
		++value;
		if( (s = strchr(value,'"')) ) *s = 0;
	}
	safestrncpy(fixed_value,value);
	for( s = fixed_value; (c = cval(s)); ++s ){
		if( isspace(c) ) *s = '_';
	}

	DEBUG2("Check_device_status: key '%s', value '%s'", line, value );
	if( !(old = GET_HASH_STR_OBJ( Devstatus, line, MEMINFO ))
		|| strcmp(old,fixed_value) ){
		if( !strcasecmp(line,"code") ){
			DEBUG4("Check_device_status: CODE '%s'", value );
			/* we now have to check to see if we log this */
			found = 0;
			for( i = 0; !found && i < LEN_LIST_OBJ(Pjl_quiet_codes); ++i ){
				found = !Globmatch( GET_ENTRY_LIST_OBJ(Pjl_quiet_codes,i), value );
			}
			DEBUG4("Check_device_status: CODE '%s' quiet code %d", value, found );
			if( !found ){
				/* ok, we log it */
				char msg[SMALLBUFFER];
				Check_code( value, msg, sizeof(msg) );
				alert = 0;
				for( i = 0; !alert && i < LEN_LIST_OBJ(Pjl_alert_codes); ++i ){
					alert = !Globmatch(GET_ENTRY_LIST_OBJ(Pjl_alert_codes,i),value);
				}
				if( alert ){
					handler = GET_HASH_STR_OBJ( Model,"pjl_alert_handler", MEMINFO );
					handler = Fix_option_str( handler, 0, 1, 1 );
				}
				DEBUG4("Check_device_code: alert %d, handler '%s'",alert,handler);
				LOGMSG(LOG_INFO)_("Check_device_status: code = %s, '%s'%s"),
					value, msg, alert?", ALERT OPERATOR":"" );
				if( handler ){
					fd = Make_tempfile();
					if( Write_fd_str( fd, msg ) < 0 || Write_fd_str(fd, "\n" ) < 0 ){
						LOGERR_DIE(LOGINFO) _("Check_device_status: write(fd %d) failed"), fd );
					}
					if( lseek(fd,0,SEEK_SET) == -1 ){
						Errorcode = JABORT;
						LOGERR_DIE(LOGINFO)_("Check_device_status: lseek failed"));
					}
					Filter_file( handler, _("ALERT"), fd, -1, 0, 0 );
					close(fd);
				}
				if( handler ) SAFEFREE( handler ); handler = 0;
			}
		} else if( !ignore ){
			DEBUG4("Check_device_status: '%s' old status '%s', new '%s'", line, old, value );
			LOGMSG(LOG_INFO)"Check_device_status: %s = '%s'", line, value );
		}
		DEBUG4("Check_device_status: '%s' setting = '%s'", line, value );
		SET_HASH_STR_OBJ( Devstatus, line, fixed_value, MEMINFO );
	}
}


/*
 * void Initialize_parms( OBJ *list, struct keyvalue *valuelist )
 *  Initialize variables from values in a parameter list
 *  This list has entries of the form key=value
 *  If the variable is a flag,  then entry of the form 'key' will set
 *    it,  and key@ will clear it.
 *
 *  list = key list, i.e. - strings of form key=value
 *  count = number of entries in key list
 *  valuelist = variables and keys to use to set them
 */

void Initialize_parms( OBJ *list, struct keyvalue *valuelist )
{
	struct keyvalue *v;
	char *arg, *convert;
	int n;
	for( v = valuelist; v && v->key; ++v ){
		if( (arg = GET_HASH_STR_OBJ( list, v->key, MEMINFO)) ){
			switch( v->kind ){
			case STRV:
				*(char **)v->var = safestrdup(arg, MEMINFO);
				break;
			case INTV:
			case FLGV:
				convert = arg;
				n = strtol( arg, &convert, 10 );
				if( convert != arg ){
					*(int *)v->var = n;
				} else {
					*(int *)v->var = (
					!strcasecmp( arg, "yes" )
					|| !strcasecmp( arg, "on" )
					|| !strcasecmp( arg, _("yes") )
					|| !strcasecmp( arg, _("on") )
					);
				}
				break;
			}
		}
	}
}

/*
 * Dump_parms( char *title, struct keyvalue *v )
 *  Dump the names, config file tags, and current values of variables
 *   in a value list.
 */
void Dump_parms( char *title, struct keyvalue *v )
{
	LOGDEBUG( "Dump_parms: %s", title );
	for( v = Valuelist; v->key; ++v ){
		switch( v->kind ){
		case STRV:
			LOGDEBUG( " '%s' (%s) STRV = '%s'", v->varname, v->key, *(char **)v->var ); break;
		case INTV:
			LOGDEBUG( " '%s' (%s) INTV = '%d'", v->varname, v->key, *(int *)v->var ); break;
		case FLGV:
			LOGDEBUG( " '%s' (%s) FLGV = '%d'", v->varname, v->key, *(int *)v->var ); break;
		}
	}
}

/*
 * Process the job  - we do this for both IF and OF modes 
 */



void Process_job( int do_pagecount, int pagecount_ps, int pagecount_pjl,
	char *pagecount_prog, int pagecount_snmp )
{
	struct stat statb;
	int in_fd, out_fd, tempfd, startpagecounter, suspend;

	Init_outbuf();

	startpagecounter = 0;

	Pjl_displayname();
	LOGMSG(LOG_INFO) _("Process_job: setting up printer"));
	in_fd = out_fd = tempfd = -1;
	if( OF_Mode ){
		if( (in_fd=dup(0)) == -1){
			Errorcode = JFAIL;
			LOGERR_DIE(LOGINFO) _("Process_job: dup(0) failed") );
		}
		if( !Appsocket && (out_fd=dup(1)) == -1){
			Errorcode = JFAIL;
			LOGERR_DIE(LOGINFO) _("Process_job: dup(1) failed") );
		}
		tempfd = Make_tempfile();
		LOGMSG(LOG_INFO) _("Process_job: starting OF mode passthrough") );
		Start_of_job(&startpagecounter, do_pagecount, pagecount_ps,
			pagecount_pjl, 0, pagecount_prog, pagecount_snmp );
		do{
			if( lseek(tempfd,0,SEEK_SET) == -1 ){
				Errorcode = JABORT;
				LOGERR_DIE(LOGINFO)_("Process_job: lseek of tempfd failed"));
			}
			if( ftruncate(tempfd,0) == -1 ){
				Errorcode = JABORT;
				LOGERR_DIE(LOGINFO)_("Process_job: ftruncate of tempfd failed"));
			}
			/* see if we need to suspend */
			if( dup2(tempfd,1) == -1 ){
				Errorcode = JABORT;
				LOGERR_DIE(LOGINFO)_("Process_job: tempfd dup2(%d,1) failed"), tempfd);
			}
			suspend = Process_OF_mode();
			if( fstat(tempfd,&statb) == -1 ){
				Errorcode = JABORT;
				LOGERR_DIE(LOGINFO)_("Process_job: stat failed"));
			}
			/* anything to write */
			if( statb.st_size ){
				double f = statb.st_size;
				if( lseek(tempfd,0,SEEK_SET) == -1 ){
					Errorcode = JABORT;
					LOGERR_DIE(LOGINFO)_("Process_job: lseek failed"));
				}
				if( dup2(tempfd,0) == -1 ){
					Errorcode = JFAIL;
					LOGERR_DIE(LOGINFO) _("Process_job: tempfd dup2() failed") );
				}
				if( Appsocket ){
					Open_device( Device );
					DEBUG1("Process_job: Appsocket device open done");
				} else if( dup2(out_fd,1) == -1 ){
					Errorcode = JFAIL;
					LOGERR_DIE(LOGINFO) _("Process_job: outfd dup2(%d,1) failed"), out_fd );
				}
				Start_of_job(0,0,0,0,1,0,0);
				LOGMSG(LOG_INFO) _("Process_job: sending %0.0f bytes of OF input"), f );
				Send_job();
				/*
				void End_of_job( int *startpagecounter, int do_pagecount, int pagecount_ps,
					int pagecount_pjl, int wait_for_end, int nested_job, int banner_page,
					char *pagecount_prog, int pagecount_snmp )
				*/
				End_of_job(0,0,0,0,Wait_for_banner,1,1,0,0);
			}
			if( suspend ){
				if( Appsocket ){
					if( !Close_appsocket ){
						int len;
						LOGMSG(LOG_INFO)_("Process_job: using shutdown on appsocket connection"));
						shutdown(1,1);
						do{
							len = Read_status_timeout(0);
						} while( len == 0 );
						/* we shut down the socket connection */
					}
					DEBUG1("Process_job: Appsocket device close");
					close(1);
				} else {
					Set_block_io(out_fd);
				}
				LOGMSG(LOG_INFO) _("Process_job: OF process suspending") );
				if( Status_fd > 0 ){
					close( Status_fd );
					Status_fd = -2;
				}
				kill(getpid(), SIGSTOP);
				LOGMSG(LOG_INFO) _("Process_job: OF process running") );
			}
			if( dup2(in_fd,0) == -1 ){
				Errorcode = JFAIL;
				LOGERR_DIE(LOGINFO) _("Process_job: infd dup2(%d,1) failed"), in_fd );
			}
		} while( suspend );
		if( !Appsocket && dup2(out_fd,1) == -1 ){
			Errorcode = JFAIL;
			LOGERR_DIE(LOGINFO) _("Process_job: outfd dup2(%d,1) failed"), out_fd );
		}
		if( tempfd > 0 ) close(tempfd); tempfd = -1;
		close(in_fd);
		close(out_fd);
		End_of_job(&startpagecounter, do_pagecount, pagecount_ps,
			pagecount_pjl, 1, 0,0, pagecount_prog, pagecount_snmp );
		LOGMSG(LOG_INFO) _("Process_job: ending OF mode passthrough") );
	} else {
		Start_of_job(&startpagecounter, do_pagecount, pagecount_ps,
			pagecount_pjl, 0, pagecount_prog, pagecount_snmp );
		LOGMSG(LOG_INFO) _("Process_job: sending job file") );
		Send_job();
		LOGMSG(LOG_INFO) _("Process_job: sent job file") );
		End_of_job(&startpagecounter, do_pagecount, pagecount_ps,
			pagecount_pjl, 1, 0, 0, pagecount_prog, pagecount_snmp );
	}
	LOGMSG(LOG_INFO) _("Process_job: done") );
}


void Start_of_job( int *startpagecounter, int do_pagecount, int pagecount_ps,
	int pagecount_pjl, int nested_job, char *pagecount_prog, int pagecount_snmp )
{
	char *s;
	OBJ *l = 0;
	struct stat statb;

	l = NEW_OBJ(l,MEMINFO);
	DEBUG1("Start_of_job: do_pagecount %d, ps %d, pjl %d, prog '%s'",
		do_pagecount, pagecount_ps, pagecount_pjl, pagecount_prog );
	if( Appsocket && fstat(1,&statb) == -1 ){
		Open_device( Device );
		DEBUG1("Start_of_job: Appsocket device open done");
	}
	if( fstat(1,&statb) == -1 ){
		Errorcode = JABORT;
		FATAL(LOGINFO) _("Start_of_job: fd 1 not open") );
	}
	Set_block_io(1);
	if( Status ){
		SET_HASH_STR_OBJ(Devstatus,"pagecount",0,MEMINFO);
		Do_sync(Sync_timeout, Sync_interval, pagecount_pjl && do_pagecount );
		if( do_pagecount && Pagecount_start && startpagecounter ){
			*startpagecounter = Do_pagecount(Pagecount_timeout,
				Pagecount_interval,
				Pagecount_poll_start?Pagecount_poll_start:Pagecount_poll,
				pagecount_ps, pagecount_pjl, pagecount_prog, pagecount_snmp );
			/* if we have Appsocket then we need to reopen the connection */
		}
		if( Appsocket ){
			DEBUG1("Start_of_job: Appsocket device close");
			close(1);
			Open_device( Device );
			DEBUG1("Start_of_job: Appsocket device open done");
		}
	}
	if( startpagecounter ){
		Do_accounting(1, 0, *startpagecounter, 0 );
	}

	Init_outbuf();
	DEBUG1("Start_of_job: doing 'init'");
	if( (s = GET_HASH_STR_OBJ( Model, "init", MEMINFO)) ){
		DEBUG1("Start_of_job: 'init'='%s'", s);
		Split_STR_OBJ( /*p*/l, /*nomod*/1, /*str*/s,
			/*type*/OBJ_T_LIST, /*linesep*/LISTSEP WHITESPACE, /*escape*/LISTSEP WHITESPACE,
			/*comment*/ 0, /*trim*/1, /*keysep*/"=",/*do_append*/',', /*lc*/1,
			/*flagvalues*/ 1, /*value_urlunesc*/0, MEMINFO );
		Resolve_list( "", l, 0, Put_pcl, 0, MAX_DEPTH );
		Clear_OBJ(l);
	}
	if( Pjl ){
		DEBUG1("Start_of_job: doing pjl");
		Put_outbuf_str( PJL_UEL_str );
		Put_outbuf_str( PJL_str );
		if( !nested_job) Pjl_job();
		Pjl_console_msg(1);
		if( (s  = GET_HASH_STR_OBJ( Model, "pjl_init", MEMINFO)) ){
			DEBUG1("Start_of_job: 'pjl_init'='%s'", s);
			Split_STR_OBJ( /*p*/l, /*nomod*/1, /*str*/s,
				/*type*/OBJ_T_LIST, /*linesep*/LISTSEP WHITESPACE, /*escape*/LISTSEP WHITESPACE,
				/*comment*/ 0, /*trim*/1, /*keysep*/"=",/*do_append*/',', /*lc*/1,
				/*flagvalues*/ 1, /*value_urlunesc*/0, MEMINFO );
			Resolve_list( "pjl_", l, 0, Put_pjl, 0, MAX_DEPTH );
			Clear_OBJ(l);
		}
		if( !OF_Mode ){
			DEBUG1("Start_of_job: 'pjl' and Zopts");
			Resolve_user_opts( "pjl_", Pjl_user_opts, Unsorted_Zopts, Zopts, Put_pjl );
		}
		DEBUG1("Start_of_job: 'pjl' and Topts");
		Resolve_user_opts( "pjl_", Pjl_user_opts, Unsorted_Topts, Model, Put_pjl );
	}
	if( Write_read_timeout( Outlen, Outbuf, Send_job_rw_timeout ) ){
		Errorcode = JFAIL;
		FATAL(LOGINFO)_("Start_of_job: timeout"));
	}
	Init_outbuf();
	FREE_OBJ(l); l = 0;
}


void End_of_job( int *startpagecounter, int do_pagecount, int pagecount_ps,
	int pagecount_pjl, int wait_for_end, int nested_job, int banner_page,
	char *pagecount_prog, int pagecount_snmp )
{
	char *s;
	OBJ *l = 0;
	int endpagecounter = 0;
	struct stat statb;

	/* make sure we clear the status */

	l = NEW_OBJ(l,MEMINFO);
	Init_outbuf();
	if( Appsocket && OF_Mode ){
		Open_device( Device );
		DEBUG1("End_of_job: Appsocket device open done");
	}
	if( fstat(1,&statb) == -1 ){
		if( Appsocket ){
			Open_device( Device );
			DEBUG1("End_of_job: Appsocket device open done");
		} else {
			Errorcode = JFAIL;
			LOGERR_DIE(LOGINFO)_("End_of_job: FD 1 IS CLOSED AND ifhp DID NOT CLOSE IT"));
		}
	}
	Set_block_io(1);
	if( Pjl ){
		DEBUG1("End_of_job: doing pjl at end");
		Put_outbuf_str( PJL_UEL_str );
		Put_outbuf_str( PJL_str );
		Pjl_console_msg(1);
		if( !nested_job ) Pjl_eoj(Jobname);
		if( (s = GET_HASH_STR_OBJ( Model, "pjl_term", MEMINFO)) ){
			DEBUG1("End_of_job: 'pjl_term'='%s'", s);
			Split_STR_OBJ( /*p*/l, /*nomod*/1, /*str*/s,
				/*type*/OBJ_T_LIST, /*linesep*/LISTSEP WHITESPACE, /*escape*/LISTSEP WHITESPACE,
				/*comment*/ 0, /*trim*/1, /*keysep*/"=",/*do_append*/',', /*lc*/1,
				/*flagvalues*/ 1, /*value_urlunesc*/0, MEMINFO );
			Resolve_list( "pjl_", l, 0, Put_pjl, 0, MAX_DEPTH );
			Clear_OBJ(l);
		}
	}
	if( (s  = GET_HASH_STR_OBJ( Model, "term", MEMINFO)) ){
		DEBUG1("End_of_job: 'term'='%s'", s);
		Split_STR_OBJ( /*p*/l, /*nomod*/1, /*str*/s,
			/*type*/OBJ_T_LIST, /*linesep*/LISTSEP WHITESPACE, /*escape*/LISTSEP WHITESPACE,
			/*comment*/ 0, /*trim*/1, /*keysep*/"=",/*do_append*/',', /*lc*/1,
			/*flagvalues*/ 1, /*value_urlunesc*/0, MEMINFO );
		Resolve_list( "", l, 0, Put_pcl, 0, MAX_DEPTH );
		Clear_OBJ(l);
	}
	if( Write_read_timeout( Outlen, Outbuf, Send_job_rw_timeout ) ){
		Errorcode = JFAIL;
		FATAL(LOGINFO)_("End_of_job: timeout"));
	}
	Init_outbuf();
	DEBUG1("End_of_job: end sync and pagecount, do_pagecount %d, ps %d, pjl %d, prog '%s', snmp %d",
		do_pagecount, pagecount_ps, pagecount_pjl, pagecount_prog, pagecount_snmp );

	/*
	 * Some badly bent or totally broken appsocket printers MUST
	 * have a shutdown rather than a close done on the connection,
	 * and the connection MUST stay open and MUST be read UNTIL
	 * the printer is finished.  This is really really maddening,
	 * but that is the way it is done.
	 *
	 * if we are doing Appsocket
	 *   and we have shutdown_appsocket
	 * THE we shutdown the Appsocket and read from it until EOF
	 *   We discard the data if Status@
	 */
	if( Appsocket ){
		if( !Close_appsocket ){
			int len;
			DEBUG1("End_of_job: Appsocket device shutdown");
			shutdown(1,1);
			do{
				len = Read_status_timeout( 0 );
			} while( len == 0 );
		}
		DEBUG1("End_of_job: Appsocket device close");
		close(1);
		/* the printer may not respond until it detects the end condition */
		if( Status && Close_appsocket && Snmp_fd && Snmp_wait_after_close > 0 ){
			sleep(Snmp_wait_after_close);
		}
		/* and yet another special case */
		if( Status && wait_for_end && (Snmp_fd || cval(Waitend) == '|') ){
			Do_waitend(Waitend_timeout, Waitend_interval,
				Ps_ctrl_t?Waitend_ctrl_t_interval:0, banner_page );
		}
	} else if( Status && wait_for_end ){
		/* we wait for the end normally */
		Do_waitend(Waitend_timeout, Waitend_interval,
			Ps_ctrl_t?Waitend_ctrl_t_interval:0, banner_page );
	}

	/* now get the pagecounter value */
	if( Status && do_pagecount && Pagecount_end && startpagecounter ){
		SET_HASH_STR_OBJ(Devstatus,"pagecount",0,MEMINFO);
		endpagecounter = Do_pagecount(Send_job_rw_timeout, Pagecount_interval,
			Pagecount_poll_end?Pagecount_poll_end:Pagecount_poll,
			pagecount_ps, pagecount_pjl, pagecount_prog, pagecount_snmp );
	}

	if( Pjl && Pjl_console ){
		if( Appsocket ){
			/* we shut down the socket connection */
			DEBUG1("End_of_job: Appsocket device close");
			close(1);
			DEBUG1("End_of_job: Appsocket device open done");
			Open_device( Device );
		}
		Init_outbuf();
		Pjl_console_msg(0);
		DEBUG1("End_of_job: clearing console at end");
		Put_outbuf_str( PJL_UEL_str );
		Write_read_timeout( Outlen, Outbuf, Send_job_rw_timeout );
		Init_outbuf();
	}

	if( startpagecounter ){
		time_t current_t;
		int elapsed, n;

		elapsed = n = 0;
		time( &current_t );
		elapsed = current_t - Start_time;
		if( Pagecount_start && Pagecount_end ){
			n = endpagecounter - *startpagecounter;
		}
		Do_accounting(0, elapsed, endpagecounter, n );
	}

	if( Appsocket ){
		/* we shut down the socket connection */
		DEBUG1("End_of_job: Appsocket device close");
		close(1);
	}
	FREE_OBJ(l); l = 0;
}

/*
 * Find_in_list( OBJ *list, char *str )
 *  - find the string in the list
 */

int Find_in_list( OBJ *list, const char *str )
{
	int listindex, listcount;;
	for( listindex = 0, listcount = LEN_LIST_OBJ(list);
			listindex < listcount; ++listindex ){
		if( !safestrcmp(GET_ENTRY_LIST_OBJ(list,listindex), str ) ){
			return( 1 );
		}
	}
	return( 0 );
}

/*
 * Put_pjl( char *s )
 *  write pjl out to the output buffer
 *  check to make sure that the line is PJL code only
 *  and that the PJL option is in a list
 */

void Put_pjl( char *s )
{
	OBJ *l = 0, *wl = 0;
	char *str = 0;
	int i;

	DEBUG4("Put_pjl: orig '%s'", s );
	if( s == 0 || *s == 0 ) return;
	l = NEW_OBJ(l,MEMINFO);
	wl = NEW_OBJ(wl,MEMINFO);
	str = Fix_option_str( s, 0, 1, 0 );
	if( str ){
		Split_STR_OBJ( /*p*/l, /*nomod*/1, /*str*/str,
		/*type*/OBJ_T_LIST, /*linesep*/"\n", /*escape*/"\n",
		/*comment*/ 0, /*trim*/1, /*keysep*/"=",/*do_append*/',', /*lc*/1,
		/*flagvalues*/ 1, /*value_urlunesc*/0, MEMINFO );
	}
	for( i = 0; i < LEN_LIST_OBJ(l); ++i ){
		/* check for valid PJL */
		char *out = GET_ENTRY_LIST_OBJ(l,i);
		wl = NEW_OBJ(wl, MEMINFO);
		Split_STR_OBJ( /*p*/wl, /*nomod*/1, /*str*/out,
		/*type*/OBJ_T_LIST, /*linesep*/WHITESPACE, /*escape*/WHITESPACE,
		/*comment*/ 0, /*trim*/1, /*keysep*/"=",/*do_append*/',', /*lc*/1,
		/*flagvalues*/ 1, /*value_urlunesc*/0, MEMINFO );
		if( LEN_LIST_OBJ(wl) == 0 ) continue;
		s = GET_ENTRY_LIST_OBJ(wl,0);
		if( strcmp("@PJL", s ) ) continue;
		/* key must be in PJL_ONLY list if supplied, and NOT in
		 * PJL_ACCEPT
		 */
		if( LEN_LIST_OBJ(wl) > 1 ){
			s = GET_ENTRY_LIST_OBJ(wl,1);
			lowercase(s);
			DEBUG4("Put_pjl: checking '%s' for support", s );
			if( (LEN_HASH_OBJ(Pjl_only) && !GET_HASH_IVAL_OBJ( Pjl_only, s))
				|| (LEN_HASH_OBJ(Pjl_except) && GET_HASH_IVAL_OBJ( Pjl_except, s ))
				){
				continue;
			}
		}
		DEBUG4("Put_pjl: ok '%s', output '%s'", s, out );
		Put_outbuf_str( out );
		Put_outbuf_str( "\n" );
	}
	if( str ) SAFEFREE(str); str = 0;
	FREE_OBJ(wl); wl = 0;
	FREE_OBJ(l); l = 0;
}

void Put_pcl( char *s )
{
	DEBUG4("Put_pcl: orig '%s'", s );
	/* char *Fix_option_str( char *str, int remove_ws, int trim, int one_line ) */
	s = Fix_option_str( s, 1, 1, 0 );
	DEBUG4("Put_pcl: final '%s'", s );
	if( !ISNULL(s) ){
		Put_outbuf_str( s );
	}
	if(s)SAFEFREE(s);s=0;
}


void Put_ps( char *s )
{
	DEBUG4("Put_ps: orig '%s'", s );
	s = Fix_option_str( s, 0, 0, 0 );
	DEBUG4("Put_ps: final '%s'", s );
	if( !ISNULL(s) ){
		Put_outbuf_str( s );
		Put_outbuf_str( "\n" );
	}
	if(s)SAFEFREE(s);s=0;
}


void Put_fixed( char *s )
{
	DEBUG4("Put_fixed: orig '%s'", s );
	s = Fix_option_str( s, 0, 0, 0 );
	DEBUG4("Put_fixed: final '%s'", s );
	if( !ISNULL(s) ){
		Put_outbuf_str( s );
	}
	if(s)SAFEFREE(s);s=0;
}


/*
 * Set_non_block_io(fd)
 * Set_block_io(fd)
 *  Set blocking or non-blocking IO
 *  Dies if unsuccessful
 * Get_nonblock_io(fd)
 *  Returns O_NONBLOCK flag value
 */

int Get_nonblock_io( int fd )
{
	int mask;
	/* we set IO to non-blocking on fd */

	mask = fcntl( fd, F_GETFL, 0 );
	if( mask != -1 ) mask &= O_NONBLOCK;
	DEBUG4("Get_nonblock_io: fd %d, current flags 0x%x, O_NONBLOCK=0x%x",fd, mask, O_NONBLOCK);
	return( mask );
}

void Set_nonblock_io( int fd )
{
	int omask;
	/* we set IO to non-blocking on fd */

	if( (omask = fcntl( fd, F_GETFL, 0 ) ) == -1 ) return;
	omask |= O_NONBLOCK;
	fcntl( fd, F_SETFL, omask );
}

void Set_block_io( int fd )
{
	int omask;
	/* we set IO to blocking on fd */

	if( (omask = fcntl( fd, F_GETFL, 0 ) ) == -1 ) return;
	omask &= ~O_NONBLOCK;
	fcntl( fd, F_SETFL, omask );
}


/**************************************************************
 * 
 * signal handling:
 * SIGALRM should be the only signal that terminates system calls;
 * all other signals should NOT terminate them.
 * This signal() emulation function attepts to do just that.
 * (Derived from Advanced Programming in the UNIX Environment, Stevens, 1992)
 *
 **************************************************************/


/* solaris 2.3 note: don't compile this with "gcc -ansi -pedantic";
 * due to a bug in the header file, struct sigaction doesn't
 * get declared. :(
 */

/* plp_signal will set flags so that signal handlers will continue
 * note that in Solaris,  you MUST reinstall the
 * signal hanlders in the signal handler!  The default action is
 * to try to restart the system call - note that the code should
 * be written so that you check for error returns, and continue
 * so this is merely a convenience.
 */

plp_sigfunc_t plp_signal (int signo, plp_sigfunc_t func)
{
#ifdef HAVE_SIGACTION
	struct sigaction act, oact;

	act.sa_handler = func;
	(void) sigemptyset (&act.sa_mask);
	act.sa_flags = 0;
# ifdef SA_RESTART
	act.sa_flags |= SA_RESTART;             /* SVR4, 4.3+BSD */
# endif
	if (sigaction (signo, &act, &oact) < 0) {
		return (SIG_ERR);
	}
	return (plp_sigfunc_t) oact.sa_handler;
#else
	/* sigaction is not supported. Just set the signals. */
	return (plp_sigfunc_t)signal (signo, func); 
#endif
}

/* plp_signal_break is similar to plp_signal,  but will cause
 * TERMINATION of a system call if possible.  This allows
 * you to force a signal to cause termination of a system
 * wait or other action.
 */

plp_sigfunc_t plp_signal_break (int signo, plp_sigfunc_t func)
{
#ifdef HAVE_SIGACTION
	struct sigaction act, oact;

	act.sa_handler = func;
	(void) sigemptyset (&act.sa_mask);
	act.sa_flags = 0;
# ifdef SA_INTERRUPT
	act.sa_flags |= SA_INTERRUPT;            /* SunOS */
# endif
	if (sigaction (signo, &act, &oact) < 0) {
		return (SIG_ERR);
	}
	return (plp_sigfunc_t) oact.sa_handler;
#else
	/* sigaction is not supported. Just set the signals. */
	return (plp_sigfunc_t)signal (signo, func); 
#endif
}

/**************************************************************/

void plp_block_all_signals ( plp_block_mask *oblock )
{
#ifdef HAVE_SIGPROCMASK
	sigset_t block;

	(void) sigfillset (&block); /* block all signals */
	if (sigprocmask (SIG_SETMASK, &block, oblock) < 0)
		LOGERR_DIE(LOGINFO) _("plp_block_all_signals: sigprocmask failed"));
#else
	*oblock = sigblock( ~0 ); /* block all signals */
#endif
}


void plp_unblock_all_signals ( plp_block_mask *oblock )
{
#ifdef HAVE_SIGPROCMASK
	sigset_t block;

	(void) sigemptyset (&block); /* block all signals */
	if (sigprocmask (SIG_SETMASK, &block, oblock) < 0)
		LOGERR_DIE(LOGINFO)_("plp_unblock_all_signals: sigprocmask failed"));
#else
	*oblock = sigblock( 0 ); /* unblock all signals */
#endif
}

void plp_set_signal_mask ( plp_block_mask *in, plp_block_mask *out )
{
#ifdef HAVE_SIGPROCMASK
	if (sigprocmask (SIG_SETMASK, in, out ) < 0)
		LOGERR_DIE(LOGINFO)_("plp_set_signal_mask: sigprocmask failed"));
#else
	if( out ){
		*out = sigblock( *in ); /* block all signals */
	} else {
	 	(void)sigblock( *in ); /* block all signals */
	}
#endif
}

void plp_unblock_one_signal ( int sig, plp_block_mask *oblock )
{
#ifdef HAVE_SIGPROCMASK
	sigset_t block;

	(void) sigemptyset (&block); /* clear out signals */
	(void) sigaddset (&block, sig ); /* clear out signals */
	if (sigprocmask (SIG_UNBLOCK, &block, oblock ) < 0)
		LOGERR_DIE(LOGINFO)_("plp_unblock_one_signal: sigprocmask failed"));
#else
	*oblock = sigblock( 0 );
	(void) sigsetmask (*oblock & ~ sigmask(sig) );
#endif
}

void plp_block_one_signal( int sig, plp_block_mask *oblock )
{
#ifdef HAVE_SIGPROCMASK
	sigset_t block;

	(void) sigemptyset (&block); /* clear out signals */
	(void) sigaddset (&block, sig ); /* clear out signals */
	if (sigprocmask (SIG_BLOCK, &block, oblock ) < 0)
		LOGERR_DIE(LOGINFO)_("plp_block_one_signal: sigprocmask failed"));
#else
	*oblock = sigblock( sigmask( sig ) );
#endif
}

void plp_sigpause( void )
{
#ifdef HAVE_SIGPROCMASK
	sigset_t block;
	(void) sigemptyset (&block); /* clear out signals */
	(void) sigsuspend( &block );
#else
	(void)sigpause( 0 );
#endif
}


/***************************************************************************
 * Set up alarms so LPRng doesn't hang forever during transfers.
 ***************************************************************************/

/*
 * timeout_alarm
 *  When we get the alarm,  we close the file descriptor (if any)
 *  we are working with.  When we next do an option, it will fail
 *  Note that this will cause any ongoing read/write operation to fail
 * We then to a longjmp to the routine, returning a non-zero value
 * We set an alarm using:
 *
 * if( (setjmp(Timeout_env)==0 && Set_timeout_alarm(t,s)) ){
 *   timeout dependent stuff
 * }
 * Clear_alarm
 * We define the Set_timeout macro as:
 *  #define Set_timeout(t,s) (setjmp(Timeout_env)==0 && Set_timeout_alarm(t,s))
 */

 static plp_signal_t timeout_alarm (int sig)
{
	Alarm_timed_out = 1;
	signal( SIGALRM, SIG_IGN );
	errno = EINTR;
#if defined(HAVE_SIGLONGJMP)
	siglongjmp(Timeout_env,1);
#else
	longjmp(Timeout_env,1);
#endif
}


 static plp_signal_t timeout_break (int sig)
{
	Alarm_timed_out = 1;
	signal( SIGALRM, SIG_IGN );
}


/***************************************************************************
 * Set_timeout( int timeout, int *socket )
 *  Set up a timeout to occur; note that you can call this
 *   routine several times without problems,  but you must call the
 *   Clear_timeout routine sooner or later to reset the timeout function.
 *  A timeout value of 0 never times out
 * Clear_alarm()
 *  Turns off the timeout alarm
 ***************************************************************************/
void Set_timeout_signal_handler( int timeout, plp_sigfunc_t handler )
{
	int err = errno;
	sigset_t oblock;

	alarm(0);
	signal(SIGALRM, SIG_IGN);
	plp_unblock_one_signal( SIGALRM, &oblock );
	Alarm_timed_out = 0;
	Timeout_pending = 0;

	if( timeout > 0 ){
		Timeout_pending = timeout;
		plp_signal_break(SIGALRM, handler);
		alarm (timeout);
	}
	errno = err;
}


void Set_timeout_alarm( int timeout )
{
	Set_timeout_signal_handler( timeout, timeout_alarm );
}

void Set_timeout_break( int timeout )
{
	Set_timeout_signal_handler( timeout, timeout_break );
}

void Clear_timeout( void )
{
	int err = errno;

	signal( SIGALRM, SIG_IGN );
	alarm(0);
	Timeout_pending = 0;
	errno = err;
}

/*
 * Write_fd_len_timeout
 *  write the buffer of length len to file descriptor fd
 *  within timeout seconds (timeout < 0 means no timeout)
 *   returns:
 *    = -1  error
 *    = -JTIMEOUT  timeout
 *    = 0   no error, but why did it return 0?
 *    > 0   bytes written
 */

int Write_fd_len_timeout( int timeout, int fd, const char *msg, int len )
{
	int i = 0, err;
	if( len ){
		Set_timeout_break( timeout );
		i = write( fd, msg, len );
		err = errno;
		Clear_timeout();
		if( Alarm_timed_out ){
			i = -JTIMEOUT;
		} else if( i < 0 && ( 0
#if defined(EWOULDBLOCK)
					|| err == EWOULDBLOCK
#endif
#if defined(EAGAIN)
					|| err == EAGAIN
#endif
			) ){
				i = 0;
		}
	}
	return( i );
}

/*
 * Write_read_timeout( int readfd, int *flag,
 *	int writefd, char *buffer, int len, int timeout )
 *  Write the contents of a buffer to the file descriptor
 *   and then optionally read the status
 *  char *buffer, int len: buffer and number of bytes
 *  int timeout:  timeout in seconds
 *
 *   Returns:
 *    0 = success
 *    = -1 error
 *    = -JTIMEOUT timeout
 */

int Write_read_timeout( int len, char *buffer, int timeout )
{
	time_t start_t, current_t;
	int elapsed, m, left, err;
	struct timeval timeval, *tp;
    fd_set readfds, writefds; /* for select */
	int fd = 1;

	DEBUG4( "Write_read_timeout: fd %d, Status %d, Poll_for_status %d, write(len %d) timeout %d",
		fd, Status, Poll_for_status, len, timeout );

	time( &start_t );

	/* do we do only a write ? */
	if( len <= 0 ) return 0;
	if( Status == 0 ){
		DEBUG4( "Write_read_timeout: blocking write len %d", len );
		Set_block_io( 1 );
		while( len > 0 ){
			m = Write_fd_len_timeout( timeout, 1, buffer, len );
			DEBUG4( "Write_read_timeout: block write status %d", m );
			if( m < 0 ){
				LOGERR(LOG_INFO) _("Write_read_timeout: write error" ));
				len = m;
			} else {
				len -= m;
			}
		}
	} else if( Poll_for_status ){
		/* we need to write a bit, then check for status */
		DEBUG4( "Write_read_timeout: poll write len %d", len );
		m = Read_status_timeout(-1);
		DEBUG1("Write_read_timeout: poll Read_status_timeout returned %d", m );
		while( m >= 0 && len > 0 ){
			left = 0;
			if( timeout > 0 ){
				time( &current_t );
				elapsed = current_t - start_t;
				left = timeout - elapsed;
				DEBUG4("Write_read_timeout: poll timeout left %d", left );
				if( left <= 0 ){
					m = -JTIMEOUT;
					break;
				}
			}
			DEBUG1("Write_read_timeout: poll writing %d", len );
			/* try to write 'len' and then check for status */
			/* note: if you are doing this on a bidirectional
			 * parallel port
			 * then you should get an early termination on the
			 * write when status is pending for read.  If not,
			 * then you may need to resort to using non-blocking IO or
			 * set up a really short interval.  We set the interval
			 * between status checks at 10 seconds.
			 */
			Set_block_io( 1 );
			m = Write_fd_len_timeout( 10, 1, buffer, len );
			DEBUG4( "Write_read_timeout: poll write status %d", m );
			if( m < 0 ){
				LOGERR(LOG_INFO) _("Write_read_timeout: EOF or TIMEOUT on output"));
				if( m == -JTIMEOUT ) m = 0;
			} else {
				len -= m;
				buffer += m;
				time( &start_t );
			}
			m = Read_status_timeout(-1);
			if( m < 0 ){
				LOGERR(LOG_INFO) _("Write_read_timeout: EOF on input"));
			}
		}
		if( m < 0 ){
			len = m;
		}
	} else while( len > 0 ){
		if( timeout > 0 ){
			time( &current_t );
			elapsed = current_t - start_t;
			left = timeout - elapsed;
			DEBUG4("Write_read_timeout: timeout left %d", left );
			if( left <= 0 ){
				len = -JTIMEOUT;
				break;
			}
			memset( &timeval, 0, sizeof(timeval) );
			timeval.tv_sec = left;
			tp = &timeval;
		} else {
			tp = 0;
		}
		FD_ZERO( &writefds );
		FD_ZERO( &readfds );
		m = 0;
		if( len > 0 ){
			FD_SET( 1, &writefds );
			DEBUG4("Write_read_timeout: setting write fd %d", 1);
			if( m <= 1 ) m = 2;
		}
		if( Status >= 0 ){
			FD_SET( fd, &readfds );
			if( m <= fd ) m = fd+1;
			FD_SET( 1, &readfds );
			if( m <= 1 ) m = 1+1;
			DEBUG4("Write_read_timeout: setting read fd %d", fd );
		}
		DEBUG4("Write_read_timeout: starting rw select - max %d", m );
		errno = 0;
		m = select( m,
			FD_SET_FIX((fd_set *))&readfds,
			FD_SET_FIX((fd_set *))&writefds,
			FD_SET_FIX((fd_set *))0, tp );
		err = errno;
		DEBUG4("Write_read_timeout: reading and writing, select returned %d, errno %d",
			m, err );
		if( m < 0 ){
			/* error */
			if( err != EINTR ){
				Errorcode = JFAIL;
				errno = err;
				LOGERR_DIE(LOGINFO) _("Write_read_timeout: select error") );
			}
			continue;
		} else if( m == 0 ){
			/* timeout */
			continue;
		}
		if( Status && (FD_ISSET( fd, &readfds ) || FD_ISSET( 1, &readfds )) ){
			m = Read_status_timeout(-1);
			err = errno;
			DEBUG1("Write_read_timeout: Read_status_timeout returned %d", m );
			if( m < 0 ){
				errno = err;
				LOGERR(LOG_INFO)_("Write_read_timeout: read from printer failed"));
				len = -1;
				break;
			}
		}
		if( FD_ISSET( 1, &writefds ) ){
			DEBUG4("Write_read_timeout: write possible on fd %d", 1 );
			Set_nonblock_io( 1 );
			errno = 0;
			m = write( 1, buffer, len );
			err = errno;
			Set_block_io( 1 );
			DEBUG4("Write_read_timeout: write() returned %d", m );
			errno = err;
			if( m < 0 ){
				if( 0
#if defined(EWOULDBLOCK)
					|| err == EWOULDBLOCK
#endif
#if defined(EAGAIN)
					|| err == EAGAIN
#endif
#if !defined(EWOULDBLOCK) && !defined(AGAIN)
#error  No definition for EWOULDBLOCK or EAGAIN
#endif
					){
					DEBUG4( "Write_read_timeout: write would block but select says not!" );
					errno = err;
				} else if( err == EINTR ){
					/* interrupted, nothing written */
					DEBUG4( "Write_read_timeout: write interrupted" );
					errno = err;
				} else {
					LOGERR(LOG_INFO) _("Write_read_timeout: write error") );
					len = -1;
					errno = err;
				}
			} else {
				time( &start_t );
				len -= m;
				buffer += m;
			}
		}
	}
	DEBUG4("Write_read_timeout: returning %d", len );
	return( len );
}

/*
 * int Read_status_timeout( int timeout )
 * Read status information from printer
 *
 *  if timeout == -1 then we do a quick poll
 *    we are called with this only if there is quaranteed
 *    data to read OR we will do a nonblocking read OR we will
 *    timeout REALLY quickly... 1 second.  This is ugly ugly ugly
 *    but the only way that you can make polling a parallel port
 *    under Linux work.
 *
 *  if timeout < 0  then we do not block
 *  if timeout == 0 then we block forever
 *  if timeout > 0 then block for timeout
 *
 *  Return:
 *    0   - status read
 *    -1  - error or EOF
 *    -JTIMEOUT  - timeout
 */

int Read_status_timeout( int timeout )
{
	int count = 0, err;
	char monitorbuff[SMALLBUFFER];
	int fd = 1;
	struct stat statb;

	/* get the character we might have read */
	if( Peek_char >= 0 ){
		monitorbuff[0] = Peek_char;
		Put_inbuf_len( monitorbuff, 1 );
		Get_inbuf_str();
		Peek_char = -1;
	}
	
	/* we read from stdout */
	err = 0;
	monitorbuff[0] = 0;
	/* if we have timeout < 0, and are not polling then we
	 * do nonblocking read.
	 */
	if( timeout < 0 ){
		errno = 0;
		Set_nonblock_io( fd );
		count = read( fd, monitorbuff, sizeof(monitorbuff) - 1 );
		err = errno;
		Set_block_io( fd );
		/* if we read 0, then we have EOF */
		DEBUG2("Read_status_timeout: timeout %d, nonblocking read result %d", timeout, count );
		if( count == 0 ){
			if( Poll_for_status ){
				/* handles the status case with no timeout, when we want REALLY
				 * short timeouts but do not want to spin wait and eat CPU time
				 * doing non-blocking reads.
				 */
				DEBUG2("Read_status_timeout: waiting %d msec", Dev_sleep);
				plp_usleep( Dev_sleep * 1000 );
				count = 0;
			} else if( !Ignore_eof ){
				count = -1;
				DEBUG2("Read_status_timeout: EOF, timeout %d", timeout );
			}
		} else if( count == -1 && ( 0
		/* if we block, then we need to try again */
#if				 defined(EWOULDBLOCK)
				|| err == EWOULDBLOCK
#endif
#if				 defined(EAGAIN)
				|| err == EAGAIN
#endif
		)){
			count = 0;
		}
	} else {
		if( Snmp_fd ){
			struct timeval tm, *tmptr = 0;
			fd_set readfds;
			int n, m;
			memset( &tm, 0, sizeof(tm) );
			tm.tv_sec = timeout;
			if( timeout ) tmptr = &tm;
			m = 0;
			FD_ZERO( &readfds );
			if( !fstat(1,&statb) ){
				FD_SET( 1, &readfds );
				m = 1+1;
			}
			FD_SET( Snmp_fd, &readfds );
			if( m <= Snmp_fd ) m = Snmp_fd+1;
			n = select( m,
				FD_SET_FIX((fd_set *))&readfds,
				FD_SET_FIX((fd_set *))(0),
				FD_SET_FIX((fd_set *))(0),
				tmptr );
			if( n < 0 ){
				Errorcode = JFAIL;
				LOGERR_DIE(LOG_INFO)"Read_status_timeout: select error");
			} else if( n == 0 ){
				DEBUG2("Read_status_timeout: select timeout" );
				count = -JTIMEOUT;
			} else {
				if( FD_ISSET( Snmp_fd, &readfds ) ){
					count = read( Snmp_fd, monitorbuff, sizeof(monitorbuff) - 1 );
					DEBUG2("Read_status_timeout: SNMP read with timeout %d, read result %d", timeout, count );
					if( count < 0 ){
						Errorcode = JFAIL;
						LOGERR_DIE(LOG_INFO)"Read_status_timeout: error reading SNMP status");
					}
					if( count == 0 ){
						Errorcode = JFAIL;
						LOGERR_DIE(LOG_INFO)"Read_status_timeout: EOF reading SNMP status");
					}
				}
				if( count == 0 && FD_ISSET( fd, &readfds ) ){
					count = read( fd, monitorbuff, sizeof(monitorbuff) - 1 );
					DEBUG2("Read_status_timeout: device read with timeout %d, read result %d", timeout, count );
				}
			}
		} else {
			Set_block_io( fd );
			Set_timeout_break( timeout );
			count = read( fd, monitorbuff, sizeof(monitorbuff) - 1 );
			err = errno;
			Clear_timeout();
			DEBUG2("Read_status_timeout: timeout %d, blocking read result %d, alarm timeout %d",
				timeout, count, Alarm_timed_out );
		}
		if( count == 0 ){
			if( Poll_for_status ){
				/* handles the status case with no timeout, when we want REALLY
				 * short timeouts but do not want to spin wait and eat CPU time
				 * doing non-blocking reads.
				 */
				DEBUG2("Read_status_timeout: waiting %d msec", Dev_sleep);
				plp_usleep( Dev_sleep * 1000 );
				count = 0;
			} else if( !Ignore_eof ){
				count = -1;
				DEBUG2("Read_status_timeout: EOF, timeout %d", timeout );
			}
		} else if( count < 0 && Alarm_timed_out ){
			count = -JTIMEOUT;
		}
	}

	DEBUG2("Read_status_timeout: timeout %d, result count %d",
		timeout, count );

	if( count > 0 ){
		monitorbuff[count] = 0;
		DEBUG2("Read_status_timeout: read count %d, '%s'", count, monitorbuff );
		Put_inbuf_len( monitorbuff, count );
		Get_inbuf_str();
		count = 0;
	}
	return( count );
}

/*
 * void Resolve_key_val( int prefix_done, char *prefix, char *id, OBJ *values,
 *   Wr_out routine, int depth, int maxdepth )
 * - determine what to do for the specified key and value
 *   we first check to see if it is a builtin
 *   if it is, then we do not need to process further
 *   prefix_done = 0 - look up <prefix><id>
 *                   - do not look up
 *   prefix          - use <prefix><id> for id lookup
 *   id              - use <prefix><id> for id lookup
 *   value           - array of hash values - look here first for value
 *                     for id,  this may be set by recursion
 *   routine         - called with real variable value
 *   depth/maxdepth  - recursion control 
 */

int Resolve_key_val( char *prefix, char *id,
	OBJ *values, Wr_out routine, int depth, int maxdepth )
{
	char *value = 0;
	char *newid;
	int done = 0;
	int current_depth = LEN_ARRAY_OBJ(Setvals);

	/* we expand the key here */
	DEBUG4("Resolve_key_val: prefix '%s', id '%s', depth %d, maxdepth %d",
		prefix, id, depth, maxdepth);
	if( depth > maxdepth ){
		Errorcode = JABORT;
		FATAL(LOG_INFO)"Resolve_key_val: id '%s', recursion level %d greater than %d",
			id, depth, maxdepth );
	}
	/* we expand the ID if necesssary */
	newid =  Fix_option_str( id, 0, 0, 0);
	DEBUG4("Resolve_key_val: prefix '%s', id '%s', newid '%s'", prefix,id,newid);
	id = newid;

	/* we push the value onto the stack if necessary */
	if( (value = GET_HASH_STR_OBJ(values,id,MEMINFO)) ){
		OBJ *p;
		char *s = Fix_option_str( value, 0, 0, 0);
		EXTEND_ARRAY_OBJ( Setvals, depth+1, 0, MEMINFO );
		p = AT_ARRAY_OBJ( Setvals, depth );
		Clear_OBJ(p);
		SET_HASH_STR_OBJ(p,id,s,MEMINFO);
		id = VAL_SHORTSTR_OBJ(KEY_HASH_OBJ(p,0));
		value = VAL_STR_OBJ(VALUE_HASH_OBJ(p,0));
		if( s ) SAFEFREE(s); s = 0;
	}
	/* decide if it is a built-in function or simply forcing a string out */
	DEBUG4("Resolve_key_val: prefix '%s', id '%s'='%s'", prefix,id,value);
	if( !done ){
		done = Builtin( prefix, id, value, routine);
		DEBUG4("Resolve_key_val: '%s' is builtin '%d'", id, done );
	}
	if( !done && !safestrncasecmp(prefix,"pjl", 3) ){
		done = Pjl_setvar( prefix, id, value, routine);
		DEBUG4("Resolve_key_val: '%s' is PJL '%d'", id, done );
	}
	if( !done && prefix ){
		char *prefix_id = safestrdup2( prefix, id,MEMINFO);
		value = GET_HASH_STR_OBJ(values,prefix_id,MEMINFO);
		if( !value ) value = GET_HASH_STR_OBJ(Model,prefix_id,MEMINFO);
		if( prefix_id ) SAFEFREE( prefix_id ); prefix_id = 0;
	}
	if( !done && value && cval(value) == '[' ){
		OBJ *l = 0;
		l = Split_STR_OBJ( /*p*/l, /*nomod*/1, /*str*/value+1,
			/*type*/OBJ_T_LIST, /*linesep*/LISTSEP WHITESPACE, /*escape*/LISTSEP WHITESPACE,
			/*comment*/ 0, /*trim*/1, /*keysep*/"=",/*do_append*/',', /*lc*/1,
			/*flagvalues*/ 1, /*value_urlunesc*/0, MEMINFO );
		Resolve_list( prefix, l, values, routine, depth+1, maxdepth );
		FREE_OBJ(l); l = 0;
		done = 1;
	}
	DEBUG4("Resolve_key_val: done '%d', value '%s'", done, value);
	if( !done && value ){
		routine( value );
		done = 1;
	}
	TRUNC_ARRAY_OBJ(Setvals, current_depth,MEMINFO);
	if( newid ) SAFEFREE(newid); newid = 0;
	return( done );
}

int Is_flag( char *s, int *v )
{
	int n = 0;
	if( s && *s && safestrlen(s) == 1 && isdigit(cval(s)) &&
		(n = (*s == '0' || *s == '1' || *s == '@')) && v ){
		if( *s == '@' ){
			*v = 0;
		} else {
			*v = *s - '0';
		}
	}
	return( n );
}

/*
 * void Resolve_list( char *prefix, OBJ *list, OBJ *values,
 *    W_out routine, int depth, int maxdepth )
 * Resolve the actions specified by a list
 * List has the form [ xx xx xx or [xx xx xx
 *   - resolve the values by calling Resolve_key_val
 */

void Resolve_list( char *prefix, OBJ *list, OBJ *values, Wr_out routine, int depth, int maxdepth )
{
	int i, c;
	char *s, *t;
	OBJ *l = 0;
	DEBUG4("Resolve_list: prefix '%s', count %d, depth %d, maxdepth %d",
		prefix, LEN_LIST_OBJ(list), depth, maxdepth );
	if(DEBUGL4)SHORT_DUMP_OBJ("Resolve_list",list);
	for( i = 0; i < LEN_LIST_OBJ(list); ++i ){
		s = GET_ENTRY_LIST_OBJ(list,i);
		DEBUG4("Resolve_list: prefix '%s', [%d]='%s'", prefix, i, s );
		while( s && (c = cval(s)) && (isspace(c) || c == '[' || c == ']')) ++s;
		if( !ISNULL(s) ){
			if( (t = safestrchr(s,'=')) ){
				if( !values ){
					values = l = NEW_HASH_OBJ(l,MEMINFO);
				}
				Split_STR_OBJ( /*p*/values, /*nomod*/1, /*str*/s,
					/*type*/OBJ_T_HASH, /*linesep*/LISTSEP WHITESPACE, /*escape*/LISTSEP WHITESPACE,
					/*comment*/ 0, /*trim*/1, /*keysep*/"=",/*do_append*/',', /*lc*/1,
					/*flagvalues*/ 1, /*value_urlunesc*/0, MEMINFO );
				*t = 0;
			}
			Resolve_key_val( prefix, s, values, routine, depth, maxdepth );
		}
	}
	FREE_OBJ(l); l = 0;
}

/*
 * void Resolve_user_opts( char *prefix, OBJ *only, *opts,
 *		W_out routine )
 * Resolve the actions specified by a the user and allowed as options
 */

void Resolve_user_opts( char *prefix, OBJ *only,
	OBJ *list, OBJ *values, Wr_out routine )
{
	int i, in_list;
	char *id = 0, *value;
	DEBUG4("Resolve_user_opts: prefix '%s'", prefix );
	if(DEBUGL4)SHORT_DUMP_OBJ("Resolve_user_opts - only",only);
	if(DEBUGL4)SHORT_DUMP_OBJ("Resolve_user_opts - list",list);
	for( i = 0; i < LEN_LIST_OBJ(list); ++i ){
		id = GET_ENTRY_LIST_OBJ(list,i);
		value = GET_HASH_STR_OBJ( values, id, MEMINFO);
		in_list = GET_HASH_IVAL_OBJ( only, id );
		DEBUG4("Resolve_user_opts: id '%s', in_list=%d, value '%s'",
			id, in_list, value );
		if( in_list ){
			Resolve_key_val( prefix, id, values, routine, 0, MAX_DEPTH );
		}
	}
}


/*
 * char *Fix_option_str( char *str, int remove_ws, int trim, int one_line )
 *  if( remove_ws ) then remove all whitespace except for FF
 *  if( trim) find and remove all white space before and after \n
 *  if( one_line) change \n to space
 *  do escape substitutions
 */

char *Fix_option_str( char *str, int remove_ws, int trim, int one_line )
{
	char *s, *t, *value, *tempvalue, *format_char, *lookup_table;
	char num[LARGEBUFFER];
	char fmt[127];
	int c, insert, len, lookup;
	char *sq, *eq;

	if( ISNULL(str) ){ return 0; }
	str = safestrdup( str,MEMINFO);
	DEBUG5("Fix_option_str: orig '%s', remove_ws %d, trim %d, one_line %d",
		str, remove_ws, trim, one_line );
	tempvalue = 0;
	/* now we do escape sequences */
	for( s = str; (s = safestrchr( s, '\\' )); ){
		/* \xYY -> xYY
           0123    0123
		*/
           
		c = s[1];
		len = 1;
		insert = 0;

		DEBUG4("Fix_option_str: escape at '%s', '%c'", s, c );
		s[0] = c;
		if( isalpha(c) ){
			switch( c ){
			case 'r': s[0] = '\r'; break;
			case 'n': s[0] = '\n'; break;
			case 't': s[0] = '\t'; break;
			case 'f': s[0] = '\f'; break;
			/* \xNN - convert next two hex digits to character */
			case 'x':
				/* move the \xNN \NN */
				memmove(s,s+1,strlen(s));
				for( len = 0;
					len < 2 && (c = (s[len] = s[1+len])) && strchr("0123456789abcdefABCDEF", c );
					++len );
				s[len] = 0;
				DEBUG5("Fix_option_str: hex str '%s', end '%s'", s, s+len+1 );
				s[0] = strtol( s, 0, 16 );
				goto done;
			}
			goto done;
		} else if( isdigit( c ) ){
			for( len = 0;
				len < 3 && (s[len] = s[1+len]) && isdigit(cval(s+len));
				++len );
			s[len] = 0;
			DEBUG5("Fix_option_str: octal str '%s', end '%s'", s, s+len+1 );

			s[0] = strtol( s, 0, 8 );
			goto done;
		} else if( c != '%' ){
  done:
			memmove( s+1, s+len+1, safestrlen(s+len+1)+1);
			++s;
			DEBUG5("Fix_option_str: after move '%s'", s );
		} else {
			/* \%FMT{...} or \%FMT[]
				s[0] == \
				s[1] == %
				s[2] == FMT
				s[2+safestrlen(FMT)] is bracket
			*/
			DEBUG5("Fix_option_str: var sub into '%s'", s+1 );
			if( (sq = safestrpbrk( s, "[{" )) ){
				if(tempvalue) SAFEFREE(tempvalue); tempvalue = 0;
				lookup = c = sq[0];
				*sq++ = 0;
				eq = safestrpbrk( sq, "]}" );
				if( eq ){
					*eq++ = 0;
				} else {
					eq = sq + safestrlen(sq);
				}
				if( safestrchr(s+2, '%') ){
					Errorcode = JABORT;
					FATAL(LOGINFO)_("Fix_option_str: bad form '%s'"), s );
				}
				SNPRINTF(fmt, sizeof(fmt))  "%%%s", s+2 );
				DEBUG5("Fix_option_str: getting '%s' fmt '%s'", sq, fmt );
				format_char = t = fmt+safestrlen(fmt)-1; /* this is safe */
				c = cval(t);
				if( !safestrcmp( sq, "ARGV") ){
					char **argv, *s;
					value = 0;
					for( argv = Argv+1; (s = *argv ); ++argv ){
						value = safeextend3(value, " ", s, MEMINFO );
					}
					tempvalue = value;
				} else {
					if( (lookup_table = safestrchr(sq, ',')) ){
						int len;
						*lookup_table++ = 0;
						while( isspace(cval(lookup_table)) ) ++lookup_table;
						len = safestrlen(sq);
						if( len ) while( isspace(cval(sq+len-1)) ) --len;
						sq[len] = 0;
					}
					/* find the value of the variable */
					value = Find_sub_value( lookup, sq, c == 's' );
					if( value == 0 || *value == 0 ){
						value = "";
					} else {
						tempvalue = value = safestrdup(value,MEMINFO);
						DEBUG3(
							"Fix_option_str: using fmt '%s', type '%c', value '%s'",
							fmt, c, value );
						/* trim the string */
						while(isspace(cval(value))) ++value;
						for( t = value+safestrlen(value); t > value && isspace(cval(t-1)); --t );
						*t = 0;
						if( value[0] == '[' ){
							++value;
							while(isspace(cval(value))) ++value;
							if( t > value && cval(t-1) == ']' ) *t = 0;
							for( t = value+safestrlen(value); t > value && isspace(cval(t-1)); --t );
						}
					}
					/* use the value as an index into the lookup table */
					if( !ISNULL(lookup_table) ){
						OBJ *l = 0;
						char *s = GET_HASH_STR_OBJ( Model, lookup_table,MEMINFO);
						DEBUG3("Fix_option_str: value '%s', lookup '%s' = '%s'",
						value, lookup_table, s );
						l = Split_STR_OBJ( /*p*/l, /*nomod*/1, /*str*/s,
						/*type*/OBJ_T_HASH, /*linesep*/LISTSEP WHITESPACE, /*escape*/LISTSEP WHITESPACE,
						/*comment*/ 0, /*trim*/1, /*keysep*/"=",/*do_append*/',', /*lc*/1,
						/*flagvalues*/ 1, /*value_urlunesc*/0, MEMINFO );
						value = GET_HASH_STR_OBJ(l,value,MEMINFO);
						DEBUG3("Fix_option_str: fixing value '%s'", value );
						t = Fix_option_str( value, remove_ws, trim, one_line );
						if( tempvalue ) SAFEFREE(tempvalue);
						value = tempvalue = t;
						FREE_OBJ(l); l = 0;
					} else if( !ISNULL(value) ){
						DEBUG3("Fix_option_str: fixing value '%s'", value );
						t = Fix_option_str( value, remove_ws, trim, one_line );
						if( tempvalue ) SAFEFREE(tempvalue);
						value = tempvalue = t;
					}
				}
				DEBUG3("Fix_option_str: fixed value '%s'", value );
				num[0] = 0;
				switch( c ){
				case 'o': case 'd': case 'x':
					SNPRINTF(num, sizeof(num))  fmt, strtol(value, 0, 0));
					break;
				case 'f': case 'g': case 'e':
					SNPRINTF(num, sizeof(num))  fmt, strtod(value, 0));
					break;
				case 's':
					SNPRINTF(num, sizeof(num))  fmt, value);
					break;
				/*
				 * handle the string things in a special manner
				 *  U = uppercase string
				 *  M = uppercase first letter, lowercase reset
				 *  L = lowercase string
				 */
				case 'U':
					*format_char = 's';
					SNPRINTF(num, sizeof(num))  fmt, value);
					uppercase(num);
					break;
				case 'M':
					*format_char = 's';
					SNPRINTF(num, sizeof(num))  fmt, value);
					lowercase(num);
					if( num[0] && islower(cval(num)) ) num[0] = toupper(cval(num));
					break;
				case 'L':
					*format_char = 's';
					SNPRINTF(num, sizeof(num))  fmt, value);
					lowercase(num);
					break;
				case 'T':
					*format_char = 's';
					/* we need to translate the value */
					SNPRINTF(num, sizeof(num))  fmt, value);
					lowercase(num);
					break;
				}
				if( tempvalue ) SAFEFREE(tempvalue); tempvalue = 0;
				*s = 0;
				len = safestrlen(str)+safestrlen(num);
				DEBUG5("Fix_option_str: first '%s', num '%s', rest '%s'",
					str, num, eq );
				s = str;
				str = safestrdup3(str,num,eq,MEMINFO);
				SAFEFREE(s);
				s = str+len;
				DEBUG5("Fix_option_str: result '%s', next '%s'", str, s );
			}
		}
	}
	if( remove_ws ){
		for( s = t = str; (*t = *s); ++s ){
			if( !isspace(cval(t)) || cval(t) == '\014' ) ++t;
		}
		*t = 0;
	}
	if( trim ){
		while( isspace(cval(str)) ) memmove( str, str+1, safestrlen(str+1)+1 );
		for( s = str; (s = safestrpbrk( s, "\n" )); s = t ){
			for( t = s; t > str && isspace(cval(t-1)); --t );
			if( t != s ){
				memmove( t, s, safestrlen(s)+1 );
				s = t;
			}
			for( t = s+1; isspace(cval(t)); ++t );
			if( t != s+1 ){
				memmove( s+1, t, safestrlen(t)+1 );
				t = s+1;
			}
		}
	}
	if( one_line ){
		for( s = str; (s = safestrpbrk( s, "\t\n" )); ++s ){
			*s = ' ';
		}
	}
	if( ISNULL(str) ){
		if( str ) SAFEFREE(str); str = 0;
	}
	DEBUG4("Fix_option_str: returning '%s'", str );
	return( str );
}

/*
 * char *Find_sub_value( int c, char *id, char *sep )
 *  if we have ?xxx?v1:v2 then if xxx has a value or
 *    the value is not '0' then we look up v1 else
 *    we look up v2
 *  if c == {, try the user -Z opts
 *  try user -T opts
 *  if id is single character try options
 *  try Model config
 *  default value is 0
 *  Note: this is NOT a recursive call, and the value may get overwritten
 *   by the next call.
 */

char *Find_sub_value( int c, char *id, int strval)
{
	char *s = 0;
	char *v1, *v2;
	static char retval[128];
	int i;
	DEBUG4("Find_sub_value: type '%c', id '%s'", c, id );
	if( *id == '?' ){
		char copy[128];
		strncpy(copy,id+1,sizeof(copy));
		v1 = v2 = 0;
		v1 = safestrchr(copy, '?');
		if( v1 ){
			v2 = safestrchr(v1+1, ':');
		}
		if( v2 == 0 ){
			FATAL(LOGINFO)_("Find_sub_value: bad conditional format '%s'"), id );
		}
		*v1++ = 0;
		*v2++ = 0;
		s = Find_sub_value( c, copy, 1 );
		DEBUG4("Find_sub_value: CONDITIONAL id '%s'='%s'", copy, s );
		if( !ISNULL(s) ){
			s = Find_sub_value( c, v1, 1 );
		} else {
			s = Find_sub_value( c, v2, 1 );
		}
		DEBUG4("Find_sub_value: CONDITIONAL type '%c', id '%s'='%s'", c, id, s );
		return( s );
	}
	if( !strcasecmp(id,"pid") ){
		SNPRINTF(retval, sizeof(retval)) "%d",getpid());
		s = retval;
		DEBUG4("Find_sub_value: pid type '%c', id '%s'='%s'", c, id, s );
		return( s );
	}
	if( !strcasecmp(id,"inputfile") ){
		SNPRINTF(retval, sizeof(retval)) "%s",InputFile);
		s = retval;
		DEBUG4("Find_sub_value: InputFile '%s'", s );
		return( s );
	}
	if( !strcasecmp(id, "papersize" ) ){
		char *t = GET_HASH_STR_OBJ( Model, "papersize", MEMINFO );
		OBJ *l = 0;

		l = NEW_OBJ(l,MEMINFO);
		DEBUG4("Find_sub_value: papersize '%s'", t );
		l = Split_STR_OBJ( /*p*/l, /*nomod*/1, /*str*/t,
				/*type*/OBJ_T_LIST, /*linesep*/LISTSEP WHITESPACE, /*escape*/LISTSEP WHITESPACE,
				/*comment*/ 0, /*trim*/1, /*keysep*/"=",/*do_append*/',', /*lc*/1,
				/*flagvalues*/ 1, /*value_urlunesc*/0, MEMINFO );
		for( i = 0; !s && i < LEN_LIST_OBJ(l); ++ i ){
			if( GET_HASH_IVAL_OBJ( Zopts, GET_ENTRY_LIST_OBJ(l,i)) ){
				s = GET_ENTRY_LIST_OBJ(l,i);
				break;
			}
		}
		for( i = 0; !s && i < LEN_LIST_OBJ(l); ++ i ){
			if( GET_HASH_IVAL_OBJ( Topts, GET_ENTRY_LIST_OBJ(l,i)) ){
				s = GET_ENTRY_LIST_OBJ(l,i);
				break;
			}
		}
		if( !s ) s = GET_HASH_STR_OBJ(Zopts,"paper", MEMINFO );
		if( !s ) s = GET_HASH_STR_OBJ(Zopts,"papersize", MEMINFO );
		if( !s ) s = GET_HASH_STR_OBJ(Topts,"paper", MEMINFO );
		if( !s ) s = GET_HASH_STR_OBJ(Topts,"papersize", MEMINFO );
		if( !s ) s = GET_HASH_STR_OBJ( Model, "papersize_default", MEMINFO );
		safestrncpy(retval,s);
		s = retval;
		FREE_OBJ(l); l = 0;
		DEBUG4("Find_sub_value: papersize type '%c', id '%s'='%s'", c, id, s );
		return( s );
	}
	if( LEN_ARRAY_OBJ(Setvals) ){
		for( i = LEN_ARRAY_OBJ(Setvals) -1; i >= 0; --i ){
			OBJ *p = AT_ARRAY_OBJ( Setvals, i );
			if( (s = GET_HASH_STR_OBJ(p,id,MEMINFO)) ) break;
		}
	}
	if( s == 0 && c == '{' ){
		s = GET_HASH_STR_OBJ( Zopts, id, MEMINFO );
		DEBUG4("Find_sub_value: from Zopts '%s'", s );
	}
	if( s == 0 ){
		s = GET_HASH_STR_OBJ( Model, id, MEMINFO );
		DEBUG4("Find_sub_value: from Model '%s'", s );
	}
	if( ISNULL(s) ){
		if( strval ){
			s = "";
		} else {
			s = "0";
		}
	}
	DEBUG4("Find_sub_value: type '%c', id '%s'='%s'", c, id, s );
	return( s );
}

/*
 * int Builtin( char* prefix, char *pr_id, char *value, Wr_out routine)
 *  - check to see if the pr_id is for a builtin operation
 *  - call the built-in with the parameters
 *  - if found, the invoked return should return return 1 
 *        so no further processing is done
 *    if not found or the return value is 0, then further processing is
 *    done.
 */

int Builtin( char* prefix, char *id, char *value, Wr_out routine)
{
	int cmp=1;
	char *s, *prefix_id = 0;
	Builtin_func r;
	struct keyvalue *v;

	DEBUG4("Builtin: looking for '%s'", id );
	for( v = Builtin_values;
		(s = v->key) && (cmp = strcasecmp(s, id));
		++v );
	if( cmp && !ISNULL(prefix) ){
		prefix_id = safestrdup2( prefix, id,MEMINFO);
		DEBUG4("Builtin: looking for '%s'", prefix_id );
		for( v = Builtin_values;
			(s = v->key) && (cmp = strcasecmp(s, prefix_id));
			++v );
	}
	if( cmp == 0 ){
		DEBUG4("Builtin: found '%s'", s );
		r = (Builtin_func)(v->var);
		cmp = (r)( prefix, id, value, routine);
	} else {
		cmp = 0;
	}
	DEBUG4("Builtin: returning '%d'", cmp );
	if( prefix_id){ SAFEFREE(prefix_id); prefix_id = 0; }
	return( cmp );
}

/***************************************************************************
 * Decode_status (plp_status_t *status)
 * returns a printable string encoding return status
 ***************************************************************************/

const char *Decode_status (plp_status_t *status)
{
    static char msg[128];

	int n;
    *msg = 0;		/* just in case! */
    if (WIFEXITED (*status)) {
		n = WEXITSTATUS(*status);
		if( n > 0 && n < 32 ) n += JFAIL-1;
		(void) SNPRINTF (msg, sizeof(msg))
		"exit status %d", WEXITSTATUS(*status) );
    } else if (WIFSTOPPED (*status)) {
		(void) strcpy(msg, "stopped");
    } else {
		(void) SNPRINTF (msg, sizeof(msg)) "died%s", WCOREDUMP (*status) ? " and dumped core" : "");
		if (WTERMSIG (*status)) {
			(void) SNPRINTF(msg+safestrlen(msg), sizeof(msg)-safestrlen(msg))
				 ", %s", Sigstr ((int) WTERMSIG (*status)));
		}
    }
    return (msg);
}

/***************************************************************************
 * plp_usleep() with select - simple minded way to avoid problems
 ***************************************************************************/
int plp_usleep( int i )
{
	struct timeval t;
	DEBUG3("plp_usleep: starting usleep %d", i );
	if( i > 0 ){
		memset( &t, 0, sizeof(t) );
		t.tv_usec = i%1000000;
		t.tv_sec = i/1000000;
		DEBUG3("plp_usleep: %d sec, %d microsec", t.tv_sec, t.tv_usec );
		i = select( 0,
			FD_SET_FIX((fd_set *))(0),
			FD_SET_FIX((fd_set *))(0),
			FD_SET_FIX((fd_set *))(0),
			&t );
		DEBUG3("plp_usleep: select done, status %d, errno %d, '%s'", i, errno, Errormsg(errno) );
	}
	return( i );
}

void Strip_leading_spaces ( char **vp )
{
	char *s = *vp;
	if( s ){
		do{
			while( isspace( cval(s) ) ) memmove(s,s+1,safestrlen(s+1)+1); 
			if( (s = safestrchr( s, '\n' )) ) ++s;
		} while(s);
		if( **vp == 0 ) *vp = 0;
	}
}
  

int Font_download( char* prefix, char *id, char *value, Wr_out routine)
{
	char *dirname = 0, *filename, *s;
	char buffer[LARGEBUFFER];
	int n, fd, i;
	OBJ *fontnames = 0;

	DEBUG2("Font_download: prefix '%s', id '%s', value '%s'",prefix, id, value );

	s = safestrdup2(prefix,"fontdir",MEMINFO);
	DEBUG4("Font_download: directory name '%s'", s );
	dirname = GET_HASH_STR_OBJ( Model, s, MEMINFO );
	if(s) SAFEFREE(s); s = 0;
	if( !dirname ){
		LOGERR(LOG_INFO)_("Font_download: no value for %sfontdir"), prefix);
		return(1);
	}

	/* if there is a value, then use it */
	if( !ISNULL(value) && (filename = Fix_option_str(value,1,1,1)) ){
		DEBUG2("Font_download: fixed value '%s'", filename );
		fontnames = Split_STR_OBJ( /*p*/fontnames, /*nomod*/1, /*str*/filename,
			/*type*/OBJ_T_LIST, /*linesep*/LISTSEP WHITESPACE, /*escape*/LISTSEP WHITESPACE,
			/*comment*/ 0, /*trim*/1, /*keysep*/"=",/*do_append*/',', /*lc*/1,
			/*flagvalues*/ 1, /*value_urlunesc*/0, MEMINFO );
		if( filename ) SAFEFREE(filename); filename = 0;
	} else {
		/* if there is a 'font' value, then use it */
		filename = Find_sub_value( '\173', "font", 1 ); /* the left curly */
		fontnames = Split_STR_OBJ( /*p*/fontnames, /*nomod*/1, /*str*/filename,
			/*type*/OBJ_T_LIST, /*linesep*/LISTSEP WHITESPACE, /*escape*/LISTSEP WHITESPACE,
			/*comment*/ 0, /*trim*/1, /*keysep*/"=",/*do_append*/',', /*lc*/1,
			/*flagvalues*/ 1, /*value_urlunesc*/0, MEMINFO );
	}
	if( LEN_LIST_OBJ(fontnames) == 0 ){
		LOGMSG(LOG_INFO)_("Font_download: no 'font' value"));
		FREE_OBJ( fontnames); fontnames = 0;
		return(1);
	}
	filename = 0;
	for( i = 0; i < LEN_LIST_OBJ(fontnames); ++i ){
		char *t;

		if( filename ) SAFEFREE(filename); filename = 0;
		s = GET_ENTRY_LIST_OBJ(fontnames,i);
		/* allow only files in font directory */
		if( (t = safestrrchr(s,'/')) ) s = t+1;
		filename = safestrdup3(dirname,"/",s,MEMINFO);
		DEBUG4("Font_download: filename '%s'", filename );
		if( (fd = open( filename, O_RDONLY )) >= 0 ){
			Set_max_fd(fd);
			while( (n = read(fd, buffer,sizeof(buffer))) >0 ){
				Put_outbuf_len( buffer, n );
			}
			close(fd);
		} else {
			LOGERR(LOG_INFO)"Font_download: cannot open '%s'", filename);
		}
	}
	if( filename ) SAFEFREE(filename); filename = 0;
	FREE_OBJ( fontnames); fontnames = 0;
	return(1);
}


/*
 * Use a common subroutine to build DISPLAY part - the job start
 *  could use this too.
 *  if pjl_console@, returns an empty string
 */

 char *Jobstart_str="@PJL JOB NAME = \"%s\"";
 char *Job_display=" DISPLAY = \"%s\" ";
 char *Jobend_str="@PJL EOJ NAME = \"%s\"";
 char *Jobreset_str="@PJL RESET\n";

 static void Pjl_displayname(void)
{
	char msg[64];
	char *s = 0;
	msg[0] = 0;
	if( Pjl_ready_msg ){
		DEBUG2("Pjl_displayname: pjl_ready_msg '%s'", Pjl_ready_msg);
		if( (s = Fix_option_str( Pjl_ready_msg, 0, 1, 1)) ){
			SNPRINTF(msg,sizeof(msg))  "%s", s);
			SAFEFREE(s); s = 0;
			if( msg[0] ) s = msg;
		}
	}
	if( !s ) s = Loweropts['n'-'a'];  /* try to get user or jobname */
	if( !s ) s = Upperopts['J'-'A'];
	if( !s ){
		SNPRINTF(msg,sizeof(msg))  "PID %d", getpid());
		s = msg;
	}
	SNPRINTF(Job_ready_msg,sizeof(Job_ready_msg))  "%s", s);
	if( Pjl_display_size > 0 &&  safestrlen(Job_ready_msg) > Pjl_display_size ){
		Job_ready_msg[Pjl_display_size] = 0;
	}

	SNPRINTF(Jobname,sizeof(Jobname)) "%s", s);
	DEBUG2("Pjl_displayname: raw Jobname '%s'", Jobname);
	{
		int len = strlen(Jobname), i;
		for( i = 0; i < len; ++i ){
			int c = cval(Jobname+i);
			if( !isspace(c) && !isalnum(c) ){
				Jobname[i] = '_';
			}
		}
	}
	DEBUG2("Pjl_displayname: fixed Jobname '%s'", Jobname);
	msg[0] = 0;
	if( Pjl_done_msg ){
		DEBUG2("Pjl_displayname: pjl_done_msg '%s'", Pjl_done_msg);
		if( (s = Fix_option_str( Pjl_done_msg, 0, 1, 1)) ){
			SNPRINTF(msg,sizeof(msg))  "%s", s);
		}
	}
	SNPRINTF(End_ready_msg,sizeof(End_ready_msg))  "%s", msg);
	if( Pjl_display_size > 0 &&  safestrlen(End_ready_msg) > Pjl_display_size ){
		End_ready_msg[Pjl_display_size] = 0;
	}
	DEBUG2("Pjl_displayname: End_ready_msg '%s'", End_ready_msg);
}

/*
 * int Pjl_job()
 *  - put out the JOB START string
 */

void Pjl_job()
{
	char *s, buffer[SMALLBUFFER];
	int n = 0, len;
	buffer[0] = 0;

	n = GET_HASH_IVAL_OBJ(Model,"pjl_job");
	if( n && ( (LEN_HASH_OBJ(Pjl_only) && !GET_HASH_IVAL_OBJ(Pjl_only, "job"))
			|| (LEN_HASH_OBJ(Pjl_except) && GET_HASH_IVAL_OBJ( Pjl_except, "job"))) ){
		n = 0;
	}
	DEBUG2("Pjl_job: Pjl %d, flag %d", Pjl, n );
	if( Pjl == 0 || n == 0 ){
		return;
	}
	buffer[0] = 0;
	len = safestrlen(buffer);
	SNPRINTF(buffer+len, sizeof(buffer)-len) Jobreset_str);
	len = safestrlen(buffer);
	SNPRINTF(buffer+len, sizeof(buffer)-len) Jobstart_str, Jobname );
	len = safestrlen(buffer);
	if( Pjl_console ){
		SNPRINTF(buffer+len, sizeof(buffer)-len) Job_display, Job_ready_msg );
	}

	if( (s = GET_HASH_STR_OBJ( Zopts, "startpage", MEMINFO))
		|| (s = GET_HASH_STR_OBJ( Topts, "startpage", MEMINFO)) ){
		n = atoi( s );
		if( n > 0 ){
			len = safestrlen(buffer);
			SNPRINTF(buffer+len, sizeof(buffer)-len)  " START = \"%d\"", n );
		}
	}
	if( (s = GET_HASH_STR_OBJ( Zopts, "endpage", MEMINFO))
		|| (s = GET_HASH_STR_OBJ( Topts, "endpage", MEMINFO)) ){
		n = atoi( s );
		if( n > 0 ){
			len = safestrlen(buffer); 
			SNPRINTF(buffer+len, sizeof(buffer)-len)  " END = \"%d\"", n );
		}
	}
	DEBUG2("Pjl_job: final = '%s'", buffer );
	Put_pjl( buffer );
}

/*
 * int Pjl_eoj(char *name)
 *  - put out the JOB EOJ string
 */

void Pjl_eoj(char *name)
{
	char buffer[SMALLBUFFER];
	int n = 0;
	int len;

	n = GET_HASH_IVAL_OBJ(Model,"pjl_job");
	if( n && ( (LEN_HASH_OBJ(Pjl_only) && !GET_HASH_IVAL_OBJ(Pjl_only, "job"))
			|| (LEN_HASH_OBJ(Pjl_except) && GET_HASH_IVAL_OBJ( Pjl_except, "job"))) ){
		n = 0;
	}
	DEBUG2("Pjl_eoj: Pjl %d, flag %d", Pjl, n );
	if( Pjl == 0 || n == 0 ){
		return;
	}
	buffer[0] = 0;
	len = safestrlen(buffer);
	SNPRINTF(buffer+len, sizeof(buffer)-len) Jobend_str, name );
	Put_pjl( buffer );
}

/*
 * PJL RDYMSG option
 *  pjl_console    enables messages on console
 *  pjl_console@   disables or erases messages on console
 */

 char *PJL_RDYMSG_str  = "@PJL RDYMSG DISPLAY = \"%s\" ";

void Pjl_console_msg( int start )
{
	char buffer[SMALLBUFFER], *name;

	DEBUG2("Pjl_console: flag %d, start %d, jobname '%s'",
		Pjl_console, start, Jobname );
	if( Pjl == 0 || Pjl_console == 0 ){
		return;
	}
	Pjl_displayname();
	if( start ){
		name = Job_ready_msg;
	} else {
		name = End_ready_msg;
	}
	SNPRINTF(buffer, sizeof(buffer))  PJL_RDYMSG_str, name );
	DEBUG2("Pjl_console: console msg '%s'", buffer);
	Put_pjl( buffer );
}

/*
 * PJL SET VAR=VALUE
 *  1. The variable is in the Pjl_vars_set list
 *      @PJL var = value
 *   OR the variable is in the Pjl_options_set list
 *      @PJL var value
 *  2. The variable must not be in the Pjl_vars_except list
 *     This list has entries of the form
 *        var  var var
 */

 char *PJL_SET_str = "@PJL SET %s = %s";
 char *PJL_SET_string_str = "@PJL SET %s = \"%s\"";
 char *PJL_OPTION_str = "@PJL %s %s";

int Pjl_setvar(char *prefix, char*id, char *value, Wr_out routine)
{
	int found = 0, n;
	char buffer[SMALLBUFFER], *s;

	DEBUG3( "Pjl_setvar: prefix '%s', id '%s', value '%s'",
		prefix, id, value );
	buffer[0] = 0;
	if( (s = GET_HASH_STR_OBJ( Pjl_vars_set, id,MEMINFO )) ){
		DEBUG3( "Pjl_setvar: found id '%s'='%s'", id, s );
		if( !GET_HASH_IVAL_OBJ( Pjl_vars_except, id ) ){
			DEBUG3( "Pjl_setvar: not in the except list" );
			if( strchr(s,';') ){
				/*
					we have constraint
					key=default!(usekey)TYPE;val1,val2,...
				*/
				char *value_list, *end, *default_value, *real_name, *type_name, *values;
				char *working = safestrdup(s,MEMINFO);
				default_value = 0;
				if( (end = strchr(working,'!')) ){
					default_value = working;
					*end++ = 0;
				} else {
					end = working;
				}
				real_name = 0;
				if( cval(end) == '('){
					char *t;
					real_name = end+1;
					end = strchr(end,')');
					*end++ = 0;
					for( t = real_name; !ISNULL(t) && (t = strchr(t,'+')); ++t ) *t = ' '; 
				}
				type_name = end;
				values = 0;
				if( (end = strchr(end,';')) ){
					*end++ = 0;
					values = end;
				}
				DEBUG3( "Pjl_setvar: id '%s', real_name '%s', value '%s', default_value '%s', type_name '%s', values '%s'",
					id, real_name, value, default_value, type_name, values );
				if( !value ) value = default_value;
				/*
				 * now we validate
				 */
				if( !strcasecmp(type_name,"string") ){
					DEBUG3( "Pjl_setvar: id '%s', string value '%s'", real_name?real_name:id, value?value:"" );
					SNPRINTF(buffer,sizeof(buffer)) PJL_SET_string_str, real_name?real_name:id, value?value:"" );
				} else if( !strcasecmp(type_name,"enumerated") ){
					char *t;
					found = 0;
					if( ISNULL(value) ){
						value = values;
						if( (end = strchr(values,',')) ) *end++ = 0;
						found = 1;
					} else {
						for( t = values; !found && !ISNULL(t); t = end ){
							if( (end = strchr(t,',')) ) *end = 0;
							found = !strcasecmp(value,t);
							if( end ){ *end = ','; ++end; }
						}
						if( !found ){
							char *m = 0;
							if( !strcmp(value,"1") ) m = "on";
							if( !strcmp(value,"0" ) ) m = "off";
							DEBUG3( "Pjl_setvar: value '%s', trying '%s'", value, m );
							for( t = values; m && !found && !ISNULL(t); t = end ){
								if( (end = strchr(t,',')) ) *end = 0;
								found = !strcasecmp(m,t);
								if( end ){ *end = ','; ++end; }
							}
							if( found ) value = m;
						}
					}
					if( !found ){
						t = strchr(s,';');
						++t;
						LOGMSG(LOG_INFO)_("Pjl_setvar: WARNING - value for PJL variable '%s' = '%s' is not one of '%s'"),
							id, value, t ); 
					} else {
						DEBUG3( "Pjl_setvar: id '%s', enumerated value '%s'", real_name?real_name:id, value?value:"" );
						SNPRINTF(buffer,sizeof(buffer)) PJL_SET_str, real_name?real_name:id, value?value:"" );
					}
				} else if( !strcasecmp(type_name,"range") ){
					char *t;
					found = 1;
					if( ISNULL(value) ){
						LOGMSG(LOG_INFO)_("Pjl_setvar: WARNING - missing value for PJL variable '%s'"),
							id ); 
					} else {
						double low, high, v;
						end = 0;
						low = high = v = 0;
						found = 1;
						v = strtod(value,&end);
						DEBUG3( "Pjl_setvar: v %f",  v );
						if( !end || cval(end) ){
							found = 0;
						}
						if( found ){
							low = strtod(values,&end);
							DEBUG3( "Pjl_setvar: low %f",  low );
							if( ISNULL(end) && cval(end) != ',' ){
								found = 0;
							} else {
								++end;
							}
						}
						if( found ){
							high = strtod(end,&end);
							DEBUG3( "Pjl_setvar: high %f",  high );
							if( !end ){
								found = 0;
							}
						}
						if( found ){
							if( low > high ){
								double m;
								m = low; low = high; high = m;
							}
							if( v < low || v > high ){
								found = 0;
							}
						}
						DEBUG3( "Pjl_setvar: low %f, high %f, value %f", low, high, v );
						if( !found ){
							LOGMSG(LOG_INFO)_("Pjl_setvar: WARNING - bad value for PJL variable '%s' = '%s', not in range %s"),
								id, value, values ); 
						} else {
							DEBUG3( "Pjl_setvar: id '%s', range value '%s'", real_name?real_name:id, value?value:"" );
							SNPRINTF(buffer,sizeof(buffer)) PJL_SET_str, real_name?real_name:id, value?value:"" );
						}
					}
				} else {
					LOGMSG(LOG_INFO)_("Pjl_setvar: WARNING - unknown type for PJL variable '%s' = '%s', %s"),
							id, value, type_name ); 
				}
				if( working ) free( working ); working = 0;
			} else {
				/* we do not have constraint */
				/* convert to ON/OFF if numerical value */
				if( !value ) value = s;
				s = 0;
				n = strtol(value,&s,0);
				if( s && !cval(s) ){
					if(n) value = "ON";
					if(!n) value = "OFF";
				}
				DEBUG3( "Pjl_setvar: setting '%s' = '%s'", id, value );
				SNPRINTF(buffer,sizeof(buffer)) PJL_SET_str,id,value);
				found = 1;
			}
			if( cval(buffer) ){
				if(strlen(buffer) < sizeof(buffer)-2 ){
					uppercase(buffer);
					routine(buffer);
				} else {
					Errorcode = JABORT;
					FATAL(LOGINFO)_("Pjl_setvar: PJL option too long! '%s'"), buffer );
				}
			}
			found = 1;
		}
	} else if( (s = GET_HASH_STR_OBJ( Pjl_options_set, id,MEMINFO )) ){
		DEBUG3( "Pjl_setvar: found option id '%s'='%s'", id, s );
		if( !GET_HASH_IVAL_OBJ( Pjl_vars_except, id ) ){
			DEBUG3( "Pjl_setvar: not in the except list" );
			if( !value ) value = "";
			DEBUG3( "Pjl_setvar: setting 'PJL %s %s", id, value );
			SNPRINTF(buffer,sizeof(buffer)) PJL_OPTION_str,id,value);
			uppercase(buffer);
			routine(buffer);
			found = 1;
		}
	}
	return(found);
}

int Pcl_setvar(char *prefix, char*id, char *value, Wr_out routine )
{
	int found = 0;
	char *s;

	DEBUG3( "Pcl_setvar: prefix '%s', id '%s', value '%s'",
		prefix, id, value );
	if( (s = GET_HASH_STR_OBJ( Pcl_vars_set, id,MEMINFO)) ){
		DEBUG3( "Pcl_setvar: found id '%s'='%s'", id, s );
		if( !Find_in_list( Pcl_vars_except, id ) ){
			DEBUG3( "Pcl_setvar: not in the except list" );
			if( !value ) value = s;
			DEBUG3( "Pcl_setvar: setting '%s' = '%s'", id, value );
			if( value ){
				routine(value);
			}
			found = 1;
		}
	}
	return(found);
}

/*
 * Do_sync()
 *  actual work is done here later
 *  sync=pjl  enables pjl
 *  sync=ps   enables postscript
 *  sync@     disables sync
 *  default is to use the prefix value
 */

 char *PJL_ECHO_str = "@PJL ECHO %s";
 char *CTRL_T = "\024";
 char *CTRL_D = "\004";

/*
 * void Do_sync( int sync_timeout, int sync_interval, int pagecount_pjl )
 * - get a response from the printer
 *  timeout is sync_timeout
 *  send the synchronization stuff at sync_interval seconds
 *  pagecount_pjl means that we should check for pagecount
 */

void Do_sync( int sync_timeout, int sync_interval, int pagecount_pjl )
{
	char buffer[SMALLBUFFER], name[SMALLBUFFER], *s, *sync_str;
	int len, elapsed, timeout, sync, use, use_ps, use_pjl, use_snmp,
		cx, attempt;
	time_t start_t, current_t, interval_t;
	char *use_prog = 0;
	struct stat statb;

	time( &start_t );

	cx = 0;
	sync_str = 0;

	s = Sync;
	DEBUG2("Do_sync: sync is '%s'", Sync );

	/* we see if we can do sync */
	use = use_pjl = use_ps = use_snmp = 0;
	if( !Is_flag( s, &use ) ){
		/* we check to see if the string is specifying */
		if( cval(s) == '|' ){
			use_prog = Fix_option_str( s+1, 0, 1, 1 );
		} else if( !strcasecmp(s,"pjl") ){
			use_pjl = Pjl;
		} else if( !strcasecmp(s,"ps") ){
			use_ps = Ps;
		} else if( !strcasecmp(s,"snmp") ){
			use_snmp = 1;
		}
		use = 1;
	} else if( use ){
		use_ps = Ps;
		use_pjl = Pjl;
	} else {
		return;
	}

	if( use_pjl
		&& ( (LEN_HASH_OBJ(Pjl_only) && !GET_HASH_IVAL_OBJ( Pjl_only, "echo"))
			|| (LEN_HASH_OBJ(Pjl_except) && GET_HASH_IVAL_OBJ( Pjl_except, "echo"))) ){
		use_pjl = 0;
	}
	if( use_pjl ) use_ps = 0;

	if( !use_ps && !use_pjl && !use_prog && !use_snmp ){
		Errorcode = JABORT;
		FATAL(LOGINFO)_("Do_sync: sync '%s' and method not supported"), s );
	}

	if( !sync_str && use_snmp ) sync_str = "snmp";
	if( !sync_str && use_pjl ) sync_str = "pjl echo";
	if( !sync_str && use_ps ) sync_str = "ps";
	if( !sync_str && use_prog ) sync_str = use_prog;

	LOGMSG(LOG_INFO)_("Do_sync: getting sync using '%s'%s"),
		sync_str, Snmp_fd?" and snmp":"" );

	attempt = 0;
	sync = 0;

	if( !use_snmp && Appsocket && fstat(1,&statb) == -1 ){
		Open_device( Device );
		DEBUG1("Start_of_job: Appsocket device open done");
	}

 again:

	++attempt;
	DEBUG2("Do_sync: attempt %d", attempt );

	/* get start time */
	time( &interval_t );
	Init_outbuf();
	SNPRINTF(name, sizeof(name))  "%d@%s", getpid(), Time_str(0,0));
	{ int c;
		for( s = name; (c = cval(s)); ++s ){
			if( isspace(c) || c == ':')  *s = '_';
		}
	}
	lowercase(name);
	DEBUG2("Do_sync: Ps_status_code '%s', use_pjl %d, use_ps %d",
			Ps_status_code, use_pjl, use_ps );
	if( use_pjl ){
		Put_outbuf_str( PJL_UEL_str );
		Put_outbuf_str( PJL_str );
		if( (s = GET_HASH_STR_OBJ( Model, "pjl_init", MEMINFO)) ){
			OBJ *l = 0;
			l = NEW_OBJ(l,MEMINFO);
			DEBUG1("Start_of_job: 'pjl_init'='%s'", s);
			Split_STR_OBJ( /*p*/l, /*nomod*/1, /*str*/s,
				/*type*/OBJ_T_LIST, /*linesep*/LISTSEP WHITESPACE, /*escape*/LISTSEP WHITESPACE,
				/*comment*/ 0, /*trim*/1, /*keysep*/"=",/*do_append*/',', /*lc*/1,
				/*flagvalues*/ 1, /*value_urlunesc*/0, MEMINFO );
			Resolve_list( "pjl_", l, 0, Put_pjl, 0, MAX_DEPTH );
			Clear_OBJ(l);
		}
		SNPRINTF(buffer, sizeof(buffer)) 
			PJL_ECHO_str, name );
		Put_pjl( buffer );

		SNPRINTF(buffer, sizeof(buffer)) PJL_RDYMSG_str, Job_ready_msg ); Put_pjl( buffer );

		if( pagecount_pjl ){
			Put_pjl( PJL_INFO_PAGECOUNT_str );
		}
		Put_outbuf_str( PJL_UEL_str );
	} else if( use_ps ){
		if( Ps_status_code == 0 ){
			FATAL(LOGINFO)_("Do_sync: sync '%s' and no ps_status_code value"), sync_str );
		}
		if(Pjl){
			Put_outbuf_str( PJL_UEL_str );
			if( Pjl_enter ){
				Put_outbuf_str( PJL_str );
				Put_outbuf_str( "@PJL ENTER LANGUAGE = POSTSCRIPT\n" );
			}
		}
		/* watch out for ^D as part of sync */
		if( !Appsocket && Ps_eoj && Ps_eoj_at_start ) Put_outbuf_str( CTRL_D );
		if( (s = safestrstr(Ps_status_code,"NAME")) ){ cx = *s; *s = 0; }
		Put_outbuf_str( Ps_status_code );
		Put_outbuf_str( name );
		if( s ){ *s = cx; s += 4; Put_outbuf_str( s ); };
		Put_outbuf_str("\n");
		/* watch out for ^D as part of sync */
		if( !Appsocket && Ps_eoj && Ps_eoj_at_end ) Put_outbuf_str( CTRL_D );
		if(Pjl){
			Put_outbuf_str( PJL_UEL_str );
		}
	}

	DEBUG2("Do_sync: sync string '%s'", Outbuf );

	while( !sync ){
		/* write the string */
		/* check to see if we need to read */
		time( &current_t );
		elapsed = current_t - start_t;

		/* check for master timeout */
		if( sync_timeout > 0 ){
			if( elapsed >= sync_timeout ){
				break;
			}
			timeout = sync_timeout - elapsed;
		} else {
			timeout = 0;
		}
		elapsed = current_t - interval_t;
		DEBUG3("Do_sync: timeout %d, sync_interval %d, elpased %d",
			timeout, sync_interval, elapsed );
		if( sync_interval > 0 ){
			timeout = sync_interval - elapsed;
			if( timeout <= 0 ){
				LOGMSG(LOG_INFO)_("Do_sync: printer not detected, trying again"));
				LOGMSG(LOG_INFO)_("Do_sync: Perhaps printer offline or not detected by Operating System"));
				LOGMSG(LOG_INFO)_("Do_sync: If on a parallel port then parallel port may not be bidirectional"));
				LOGMSG(LOG_INFO)_("Do_sync: Use the 'status@' filter option to suppress this check"));
				LOGMSG(LOG_INFO)_("Do_sync: See the ifhp man page or the ifhp HOWTO for details"));
				goto again;
			}
		}
		DEBUG3("Do_sync: waiting for sync, timeout %d", timeout );
		len = 0;
		if( use_prog ){
			len = Get_prog_status( Devstatus, use_prog, timeout );
			sync = GET_HASH_IVAL_OBJ(Devstatus,"sync");
		} else if( Outlen ){
			len = Write_read_timeout( Outlen, Outbuf, timeout );
			Init_outbuf();
		} else {
			len = Read_status_timeout( timeout );
		}
		DEBUG3("Do_sync: read/write result '%d'", len );
		if( Snmp_fd ){
			int i;
			for( i = 0; !sync && i < LEN_LIST_OBJ(Status_fields); ++i ){
				char *s = GET_ENTRY_LIST_OBJ(Status_fields,i);
				char *status = GET_HASH_STR_OBJ( Devstatus, s, MEMINFO);
				/* The status we get back must be in the Snmp_sync_status list,
				 * the value in the list must not start with '!' */
				DEBUG3( "Do_sync: %s '%s', Snmp_sync_status '%s'", s, status, Snmp_sync_status);
				if( (s = safestrstr( Snmp_sync_status, status ))
						&& (s == Snmp_sync_status || cval(s-1) != '!') ){
					sync = 1;
				}
			}
			if( sync ) break;
		}
		if( (s = GET_HASH_STR_OBJ( Devstatus, "echo", MEMINFO))
			&& safestrstr( s, name ) ){
			sync = 1;
			break;
		}
		if( len == -1 ){
			Errorcode = JFAIL;
			FATAL(LOG_INFO)_("Do_sync: EOF on connection"));
			break;
		}
		s = GET_HASH_STR_OBJ( Devstatus, "error", MEMINFO );
		if( !ISNULL(s) ){
			Errorcode = JFAIL;
			FATAL(LOGINFO)_("Do_sync: error '%s'"), s );
		}
	}
	DEBUG2("Do_sync: sync %d", sync );
	if( sync == 0 ){
		Errorcode = JFAIL;
		FATAL(LOGINFO)"Do_sync: no sync response from printer" );
	}
	if( use_prog ) free( use_prog ); use_prog = 0;
	LOGMSG(LOG_INFO)_("Do_sync: sync done"));
}

/*
 * void Do_waitend( int waitend_timeout, int interval )
 * - get the job status from the printer
 *   and wait for the end of job
 */

 char *PJL_USTATUS_JOB_str = "@PJL USTATUS JOB = ON";

void Do_waitend( int waitend_timeout, int waitend_interval,
	int waitend_ctrl_t_interval, int banner )
{
	char *sync_str, *opt_str, buffer[SMALLBUFFER], endname[SMALLBUFFER];
	int len, elapsed, timeout,
		use, use_pjl, use_ps, use_job, c, use_snmp,
		echo_received = 0, first_time = 1;
	time_t start_t, current_t, interval_t, response_t;
	char *status;
	char *waitend;
	char *use_prog = 0;
	struct stat statb;

	time( &start_t );
	response_t = start_t;

	SET_HASH_STR_OBJ( Devstatus, "pagecount", 0, MEMINFO );
	opt_str = (Waitend?Waitend:Sync);
	DEBUG1("Do_waitend: waitend '%s', end_status '%s', Send_job_rw_timeout %d, waitend timeout %d, interval %d, ctrl_t_interval %d",
		opt_str, End_status, Send_job_rw_timeout, waitend_timeout,
		waitend_interval, waitend_ctrl_t_interval
		);

	/* we see if we can do sync */
	use = use_pjl = use_ps = use_job = use_snmp = 0;
	sync_str = 0;
	if( !Is_flag( opt_str, &use ) ){
		/* we check to see if the string is specifying */
		if( cval(opt_str) == '|' ){
			use_prog = Fix_option_str( opt_str+1, 0, 1, 1 );
		} else if( !strcasecmp(opt_str,"pjl") ){
			use_pjl = Pjl;
		} else if( !strcasecmp(opt_str,"ps") ){
			use_ps = Ps;
		} else if( !strcasecmp( opt_str, "snmp") ){
			use_snmp = 1;
		}
		use = 1;
	} else if( use ){
		use_job = use_pjl = Pjl;
		use_ps = Ps;
	} else {
		return;
	}
	if( ISNULL(End_status) || Is_flag(End_status, &c) ){
		Errorcode = JABORT;
		FATAL(LOGINFO)_("Do_waitend: invalid endstatus value '%s'"), End_status );
	}

	if( use_pjl ){
		use_job = GET_HASH_IVAL_OBJ(Model,"pjl_job");
		if( use_job && ( (LEN_HASH_OBJ(Pjl_only) && !GET_HASH_IVAL_OBJ(Pjl_only, "job"))
				|| (LEN_HASH_OBJ(Pjl_except) && GET_HASH_IVAL_OBJ( Pjl_except, "job"))) ){
			use_job = 0;
		}
		if( !use_job && ( (LEN_HASH_OBJ(Pjl_only) && !GET_HASH_IVAL_OBJ(Pjl_only, "echo"))
				|| (LEN_HASH_OBJ(Pjl_except) && GET_HASH_IVAL_OBJ( Pjl_except, "echo"))) ){
			use_pjl = 0;
		}
		if( !use_ps && !use_pjl && !use_prog ){
			Errorcode = JABORT;
			FATAL(LOGINFO)_("Do_waitend: waitend '%s' and interactive method not supported"), opt_str );
		}
	}
	sync_str = 0;
	if( !sync_str && use_job ) sync_str = "pjl job/eoj";
	if( !sync_str && use_pjl ) sync_str = "pjl echo";
	if( !sync_str && use_ps ) sync_str = "ps";
	if( !sync_str && use_prog ) sync_str = "prog";
	if( !sync_str && use_snmp ) sync_str = "snmp";

	LOGMSG(LOG_INFO)_("Do_waitend: getting end using '%s'%s"), sync_str, Snmp_fd?" and snmp":"" );

	waitend = 0;

	/* find if we need to periodically send the waitend value */
	DEBUG3("Do_waitend: use_pjl %d, use_job %d, use_ps %d, use_snmp %d, timeout %d, interval %d",
		use_pjl, use_job, use_ps, use_snmp, waitend_timeout, waitend_interval );


	SNPRINTF(endname, sizeof(endname))  "%s PID %d",
		Time_str(1,0), getpid() );
	{ char *t; int c;
		for( t = endname; (c = cval(t)); ++t){
			if( isspace(c) || c == ':' ) *t = '_';
		}
		lowercase(endname);
	}

	DEBUG1("Do_waitend: endname '%s'", endname );

	if ( Pjl_waitend_byjobname )
		strcpy(endname, Jobname);

 again:

	Init_outbuf();
	if( use_pjl ){
		Put_outbuf_str( PJL_UEL_str );
		Put_outbuf_str( PJL_str );
		if ( Pjl_waitend_byjobname )
		{
		 	/* do nothing, just wait for the end of the real job */
		}
		else if( use_job ){
			buffer[0] = 0;
			len = safestrlen(buffer);
			SNPRINTF(buffer+len, sizeof(buffer)-len) Jobreset_str);
			SNPRINTF(buffer+len, sizeof(buffer)-len)  Jobstart_str, endname );
			if( Pjl_console ){
				len = safestrlen(buffer);
				SNPRINTF(buffer+len, sizeof(buffer)-len) Job_display, Job_ready_msg );
			}
			Put_pjl(buffer);
			Put_pjl( PJL_USTATUS_JOB_str );
			Pjl_eoj(endname);
		} else {
			SNPRINTF(buffer, sizeof(buffer))  PJL_ECHO_str, endname );
			Put_pjl(buffer);
		}
		Put_outbuf_str( PJL_UEL_str );
	} else if( use_ps ){
		if( Ps_status_code == 0 ){
			FATAL(LOGINFO)"Do_waitend: no ps_status_code value" );
		}
		if( !echo_received  || waitend_ctrl_t_interval > 0 ){
			if(Pjl){
				Put_outbuf_str( PJL_UEL_str );
				if( Pjl_enter ){
					Put_outbuf_str( PJL_str );
					Put_outbuf_str( "@PJL ENTER LANGUAGE = POSTSCRIPT\n" );
				}
			}
			if( !Appsocket && Ps_eoj && Ps_eoj_at_start ) Put_outbuf_str( CTRL_D );
			if( !echo_received ){
				char *s;
				c = 0;
				if( (s = safestrstr(Ps_status_code,"NAME")) ){ c = *s; *s = 0; }
				Put_outbuf_str( Ps_status_code );
				Put_outbuf_str( endname );
				if( s ){ *s = c; s += 4; Put_outbuf_str( s ); };
			}
			Put_outbuf_str("\n");
			if( waitend_ctrl_t_interval ){
				Put_outbuf_str( CTRL_T );
			}
			if( !Appsocket && Ps_eoj && Ps_eoj_at_end ) Put_outbuf_str( CTRL_D );
			if(Pjl){
				Put_outbuf_str( PJL_UEL_str );
			}
		}
	}
	if( Snmp_fd ){
		int i;
		if( !Appsocket && Snmp_wait_after_close > 0 ){
			LOGMSG(LOG_INFO) _("Do_waitend: sleep %d before getting SNMP status"),
				Snmp_wait_after_close );
			sleep(Snmp_wait_after_close);
		}
		/* clear these out */
		for( i = 0; i < LEN_LIST_OBJ(Status_fields); ++i ){
			char *s = GET_ENTRY_LIST_OBJ(Status_fields,i);
			SET_HASH_STR_OBJ( Devstatus, s, 0, MEMINFO);
		}
	}

	time( &interval_t );

	DEBUG3("Do_waitend: sending '%s'", Outbuf );
	Errorcode = 0;
	while( !waitend ){
		char *s, *t, *u;
		/* write the string */
		/* check to see if we need to read */
		time( &current_t );

		/* check for master timeout */
		timeout = 0;
		if( waitend_timeout > 0 ){
			elapsed = current_t - start_t;
			DEBUG3("Do_waitend: waitend timeout %d, elapsed %d",
				waitend_timeout, elapsed );
			if( elapsed >= waitend_timeout ){
				Errorcode = -JTIMEOUT;
				break;
			}
			timeout = waitend_timeout - elapsed;
		}
		if( Send_job_rw_timeout > 0 ){
			int t;
			elapsed = current_t - response_t;
			DEBUG3("Do_waitend: Send_job_rw_timeout %d, elapsed %d from last response",
				Send_job_rw_timeout, elapsed );
			if( elapsed >= Send_job_rw_timeout ){
				Errorcode = -JTIMEOUT;
				break;
			}
			t = Send_job_rw_timeout - elapsed;
			if( timeout == 0 || t < timeout ) timeout = t;
		}
		if( waitend_interval > 0 ){
			int t;
			elapsed = current_t - interval_t;
			t = waitend_interval - elapsed;
			if( t <= 0 ) goto again;
			if( timeout == 0 || t < timeout ) timeout = t;
		}
		DEBUG2("Do_waitend: timeout %d, Outlen %d '%s'", timeout, Outlen, Outlen?Outbuf:"" );
		if( use_prog ){
			len = Get_prog_status( Devstatus, use_prog, timeout );
			if( GET_HASH_IVAL_OBJ( Devstatus,"waitend") ){
				waitend = "program";
				break;
			}
		} else if( Outlen ){
			len = Write_read_timeout( Outlen, Outbuf, timeout );
			if( len == 0 ) time( &response_t );
			Init_outbuf();
		} else {
			len = Read_status_timeout( timeout );
			if( len == 0 ) time( &response_t );
		}
		DEBUG3("Do_waitend: len %d", len );
		if( len == -1 ){
			/* we have an error */
			LOGMSG(LOG_INFO) _("Do_waitend: EOF reading status") );
			break;
		}
		if(DEBUGL3)SHORT_DUMP_OBJ("Do_waitend - Devstatus", Devstatus );
		if( Snmp_fd ){
			int i;
			if( first_time ){
				/* clear these out - you will have old status from snmp */
				for( i = 0; i < LEN_LIST_OBJ(Status_fields); ++i ){
					char *s = GET_ENTRY_LIST_OBJ(Status_fields,i);
					SET_HASH_STR_OBJ( Devstatus, s, 0, MEMINFO);
				}
				first_time = 0;
			}
			for( i = 0; !waitend && i < LEN_LIST_OBJ(Status_fields); ++i ){
				char *s = GET_ENTRY_LIST_OBJ(Status_fields,i);
				status = GET_HASH_STR_OBJ( Devstatus, s, MEMINFO);
				/* The status we get back must be in the Snmp_end_status list,
				 * the value in the list must not start with '!' */
				DEBUG3( "Do_waitend: %s '%s', Snmp_end_status '%s'", s, status, Snmp_end_status);
				if( (s = safestrstr( Snmp_end_status, status ))
						&& (s == Snmp_end_status || cval(s-1) != '!') ){
					waitend = "snmp";
					SET_HASH_STR_OBJ( Devstatus, "pagecount", 0, MEMINFO );
					break;
				}
			}
			if( waitend ) break;
		}
		if( use_pjl ){
			char *e = GET_HASH_STR_OBJ( Devstatus, "echo", MEMINFO);
			s = GET_HASH_STR_OBJ( Devstatus, "job", MEMINFO);
			t = GET_HASH_STR_OBJ( Devstatus, "name", MEMINFO);
			u = GET_HASH_STR_OBJ( Devstatus, "result", MEMINFO);
			DEBUG2("Do_waitend: job '%s', name '%s', result '%s', echo '%s', endname %s",
			 s, t, u, e, endname );
			if( s && safestrstr(s,"end") && t && safestrstr(t,endname) ){
				waitend = "pjl JOB";
				break;
			}
			/* we have the job cancelled, so we retry */ 
			if( (safestrstr(s, "CANCELED"))
			  || (safestrstr(u, "CANCELED")) ){
				SET_HASH_STR_OBJ(Devstatus,"job",0,MEMINFO);
				SET_HASH_STR_OBJ(Devstatus,"result",0,MEMINFO);
				LOGMSG(LOG_INFO)_("Do_waitend: job canceled"));
				goto again;
			}
			s = GET_HASH_STR_OBJ( Devstatus, "echo", MEMINFO);
			DEBUG2("Do_waitend: echo '%s', want '%s'", s, endname );
			if( safestrstr(s,endname) ){
				LOGMSG(LOG_INFO)_("Do_waitend: ECHO END detected"));
				waitend = "pjl ECHO";
				break;
			}
		} else if( use_ps ){
			if( !echo_received ){
				s = GET_HASH_STR_OBJ( Devstatus, "echo", MEMINFO);
				echo_received = (safestrstr( s, endname ) != 0 );
				DEBUG4("Do_waitend: echo '%s', endname '%s', echo_received %d",
					s, endname, echo_received );
			}
			if( echo_received ){
				status = GET_HASH_STR_OBJ( Devstatus, "status", MEMINFO);
				/* The status we get back must be in the End_status list,
				 * the value in the list must not start with '!' */
				DEBUG4( "Do_waitend: status '%s', End_status '%s'", status, End_status);
				if( (s = safestrstr( End_status, status ))
						&& (s == End_status || cval(s-1) != '!') ){
					waitend = "ps";
					break;
				} else if( waitend_ctrl_t_interval > 0
						&& waitend_interval > waitend_ctrl_t_interval ){
					/* we wait only a short amount of time */
					waitend_interval = waitend_ctrl_t_interval;
				}
			}
			DEBUG4("Do_waitend: waitend %d", waitend );
		}
		s = GET_HASH_STR_OBJ( Devstatus, "error", MEMINFO );
		if( !ISNULL(s) ){
			Errorcode = JFAIL;
			FATAL(LOGINFO)_("Do_waitend: error '%s'"), s );
		}
	}
	if( waitend == 0 ){
		if( Errorcode == 0 ) Errorcode = JFAIL;
		FATAL(LOGINFO)_("Do_waitend: no end response from printer, timeout %d"), waitend_timeout );
	}
	if( use_prog ) free( use_prog ); use_prog = 0;
	LOGMSG(LOG_INFO)_("Do_waitend: %s detected %s END"), banner?_("BANNER PAGE"):_("JOB"), waitend );
}

/*
 * int Do_pagecount(void)
 * - get the pagecounter value from the printer
 *  We can use the PJL or PostScript method
 *  If we use PJL, then we send pagecount command, and read status
 *   until we get the value.
 *  If we use PostScript,  we need to set up the printer for PostScript,
 *   send the PostScript code,  and then get the response.
 *
 * INFO PAGECOUNT Option
 * pagecount=pjl   - uses PJL (default)
 * pagecount=ps    - uses PS
 * pagecount=snmp   - uses SNMP
 * pagecount@
 *
 */

int Check_pagecount( int *use_ps_v, int *use_pjl_v, char **use_prog_v, int *use_snmp_v )
{
	int use, use_ps, use_pjl, use_snmp;
	char *s;
	char *use_prog = 0;

	*use_ps_v = *use_pjl_v = 0;
	*use_prog_v = 0;
	if( Status == 0 ) return(0);
	s = Pagecount;
	DEBUG4("Check_pagecount: status %d, pagecount using '%s'", Status, s );

	/* we see if we can do pagecount */
	use = use_pjl = use_ps = use_snmp = 0;
	if( !Is_flag( s, &use ) ){
		/* we check to see if the string is specifying */
		if( cval(s) == '|' ){
			use_prog = Fix_option_str( s+1, 0, 1, 1 );
		} else if( !strcasecmp(s,"pjl") ){
			use_pjl = 1;
		} else if( !strcasecmp(s,"ps") ){
			use_ps = 1;
		} else if( !strcasecmp(s, "snmp") ){
			use_snmp = 1;
		}
		use = 1;
	} else if( use ){
		use_pjl = Pjl;
		use_ps = Ps;
	} else {
		return(0);
	}

	Strip_leading_spaces( &Ps_pagecount_code );
	if(use_ps && !Ps_pagecount_code){
		use_ps = 0;
	}
	if( use_pjl && ( (LEN_HASH_OBJ(Pjl_only) && !GET_HASH_IVAL_OBJ(Pjl_only, "info"))
		|| (LEN_HASH_OBJ(Pjl_except) && GET_HASH_IVAL_OBJ( Pjl_except, "info"))) ){
		use_pjl = 0;
	}
	if(use_ps && !Ps_pagecount_code){
		use_ps = 0;
	}
	if( use_pjl ) use_ps = 0;

	if( !use_ps && !use_pjl && !use_prog && !use_snmp ){
		Errorcode = JABORT;
		FATAL(LOGINFO)_("Check_pagecount: pagecount '%s' and method not supported"), s );
	}
	s = 0;
	if( !s && use_pjl ) s = "pjl info pagecount";
	if( !s && use_ps ) s = "ps script";
	if( !s && use_prog ) s = "program";
	if( !s && use_snmp ) s = "snmp info pagecount";

	LOGMSG(LOG_INFO)_("Check_pagecount: pagecount using '%s'%s"), s, (Snmp_fd&&!use_snmp)?" and snmp":"" );

	*use_ps_v = use_ps;
	*use_pjl_v = use_pjl;
	*use_prog_v = use_prog;
	*use_snmp_v = use_snmp;
	return(1);
}

int Do_pagecount( int pagecount_timeout, int pagecount_interval, int pagecount_poll,
	 int use_ps, int use_pjl, char *use_prog, int use_snmp )
{
	int pagecounter = -1, new_pagecounter = -1, len;
	int pagecount_same = 1;
	char *s;

	if( (s = GET_HASH_STR_OBJ( Devstatus, "pagecount", MEMINFO )) ){
		pagecounter = atoi( s );
	}
	DEBUG1("Do_pagecount: pagecount at start %d", pagecounter );
	if( use_prog ){
		if( !pagecounter ){
			len = Get_prog_status( Devstatus, use_prog, pagecount_timeout );
			pagecounter = GET_HASH_IVAL_OBJ( Devstatus, "pagecount" );
			DEBUG1("Do_pagecount: pagecount returned by program %d", pagecounter );
			LOGMSG(LOG_INFO)_("Do_pagecount: pagecounter %d from program"), pagecounter );
		} else {
			LOGMSG(LOG_INFO)_("Do_pagecount: pagecounter %d"), pagecounter );
		}
		return( pagecounter );
	}

	if( pagecount_poll > 0 ){
		LOGMSG(LOG_INFO)_("Do_pagecount: polling pagecounter %d times at %d second intervals, max timeout %d"),
			pagecount_poll, pagecount_interval, pagecount_timeout );
		SET_HASH_STR_OBJ( Devstatus, "pagecount", 0, MEMINFO );
		pagecounter = 0;
	}
	pagecount_same = 0;
	if( pagecounter == -1 || pagecount_poll > 1 ){
		do{
			int timeout;
			time_t start_t, now_t;
			time( & start_t );
			timeout = pagecount_timeout;
			/*
			if( pagecount_poll > 1 && pagecount_interval > 0 ){
					timeout = pagecount_interval;
			}
			*/
			DEBUG1("Do_pagecount: getting pagecount, timeout %d, pagecount_same %d, pagecount_poll %d",
				timeout, pagecount_same, pagecount_poll);
			new_pagecounter = Current_pagecounter(timeout, use_pjl, use_ps, use_snmp );
			if( new_pagecounter == -JTIMEOUT ){
				/* timeout */
				Errorcode = JTIMEOUT;
				FATAL(LOGINFO)_("Do_pagecount: timeout getting pagecount"));
			} else if( new_pagecounter == -1 ){
				Errorcode = JFAIL;
				FATAL(LOGINFO)_("Do_pagecount: IO error getting pagecount"));
			}
			if( new_pagecounter == pagecounter ){
				++pagecount_same;
			} else {
				pagecounter = new_pagecounter;
				pagecount_same = 1;
			}
			if( pagecount_same < pagecount_poll && pagecount_interval > 0 ){
				int n;
				time( &now_t );
				n = pagecount_interval - (int)(now_t - start_t);
				if( n <= 0 ) n = 1;
				DEBUG1("Do_pagecount: sleeping %d seconds", n);
				sleep( n ); 
				SET_HASH_STR_OBJ( Devstatus, "pagecount", 0, MEMINFO );
			}
		} while( pagecounter == -1 || pagecount_same < pagecount_poll);
	}
	LOGMSG(LOG_INFO)_("Do_pagecount: pagecounter %d after %d attempts"),
		pagecounter, pagecount_same );
	SET_HASH_IVAL_OBJ( Devstatus, "pagecount", pagecounter, MEMINFO );
	return( pagecounter );
}

int Current_pagecounter( int pagecount_timeout, int use_pjl, int use_ps, int use_snmp )
{
	int len, flag, elapsed, timeout, page, pagecounter = 0;
	time_t start_t, current_t, interval_t;
	char *s;

	/* get start time */
	time( & start_t );

	if(DEBUGL4)SHORT_DUMP_OBJ("Current_pagecounter - starting Devstatus",Devstatus);

 again:

	if( Appsocket && !use_snmp ){
		DEBUG1("Current_pagecounter: Appsocket device close");
		close(1);
		Open_device( Device );
		DEBUG1("Current_pagecounter: Appsocket device open done");
	}


	/* interval time */
	time( & interval_t );

	if(DEBUGL4)SHORT_DUMP_OBJ("Current_pagecounter - loop Devstatus", Devstatus);
	DEBUG1("Current_pagecounter: starting, use_pjl %d, use_ps %d, timeout %d",
		use_pjl, use_ps, pagecount_timeout );

	/* remove the old status */
	/* SET_HASH_STR_OBJ(Devstatus,"pagecount",0,MEMINFO); */

	Init_outbuf();
	if( use_pjl ){
		Put_outbuf_str( PJL_UEL_str );
		Put_outbuf_str( PJL_str );
		Put_pjl( PJL_INFO_PAGECOUNT_str );
		Put_outbuf_str( PJL_UEL_str );
	} else if( use_ps ){
		if( !Ps_pagecount_code ){
			Errorcode = JABORT;
			FATAL(LOGINFO)_("Current_pagecounter: no ps_pagecount_code config info"));
		}
		if( Pjl ){
			Put_outbuf_str( PJL_UEL_str );
			if( Pjl_enter ){
				Put_outbuf_str( PJL_str );
				Put_outbuf_str( "@PJL ENTER LANGUAGE = POSTSCRIPT\n" );
			}
		}
		/* Watch out for ^D again */
		if( !Appsocket && Ps_eoj && Ps_eoj_at_start ) Put_outbuf_str( CTRL_D );
		Put_outbuf_str( Ps_pagecount_code );
		Put_outbuf_str( "\n" );
		if( !Appsocket && Ps_eoj && Ps_eoj_at_end ) Put_outbuf_str( CTRL_D );
		if( Pjl ) Put_outbuf_str( PJL_UEL_str );
	}

	DEBUG2("Current_pagecounter: using string '%s'", Outbuf );

	page = 0;
	len = 0;
	while(!page){
		if(DEBUGL4)SHORT_DUMP_OBJ("Current_pagecounter - page wait Devstatus",Devstatus);
		if( (s = GET_HASH_STR_OBJ( Devstatus, "pagecount", MEMINFO )) ){
			if( s && isdigit(cval(s)) ){
				pagecounter = atoi( s );
				page = 1;
				break;
			}
		}
		flag = 0;
		/* write the string */
		/* check to see if we need to read */
		time( &current_t );
		elapsed = current_t - start_t;
		if( pagecount_timeout > 0 ){
			if( elapsed >= pagecount_timeout ){
				break;
			}
			timeout = pagecount_timeout - elapsed;
		} else {
			timeout = 0;
		}
		if( Outlen ){
			DEBUG1("Current_pagecounter: writing %d", Outlen );
			len = Write_read_timeout( Outlen, Outbuf, timeout );
			DEBUG1("Current_pagecounter: Write_read_timeout result %d", len );
			Init_outbuf();
		} else {
			len = Read_status_timeout( timeout );
			DEBUG1("Current_pagecounter: Read_status_timeout result %d", len );
		}
		if( len == -1 ){
			LOGMSG(LOG_INFO) _("Current_pagecounter: EOF reading status") );
			pagecounter = -1;
			break;
		} else if( len == -JTIMEOUT ){
			pagecounter = -JTIMEOUT;
			break;
		}
		s = GET_HASH_STR_OBJ( Devstatus, "error", MEMINFO );
		if( !ISNULL(s) ){
			Errorcode = JFAIL;
			FATAL(LOGINFO)_("Current_pagecounter: error '%s'"), s );
		}
	}
 done:
	DEBUG1("Current_pagecounter: page %d, pagecounter %d", page, pagecounter );
	return( pagecounter );
}


/*
 * Send_job
 * 1. we read an input buffer
 * 2. we then check to see what type it is
 * 3. we then set up the various configuration
 * 4. we then send the job to the remote end
 * Note: there is a problem when you send language switch commands
 *  to the HP 4M+, and other printers.
 * two functions.
 *
 * The following comments from hp.com try to explain the situation:
 *
 *"What is happening is the printer buffer fills with your switch
 * command and the first parts of your job. The printer does not
 * "prescan" the input looking for the <Esc>%-1234... but instead gets
 * around to it after some time has expired. During the switch, the
 * data that you have already trasmitted is discarded and is picked
 * up whereever your driver happens to be. For PostScript that's a
 * messy situation.
 * 
 * What you need to do is isolate the switch command. You can do this
 * with nulls if you have no control over the timing. 8K of them will
 * fill the largest allocated I/O buffer and then hold off the UNIX
 * system. The switch will be made and the initial remaining nulls
 * will be discarded. If you can control the timing of the data, you'll
 * have to experiment with the correct time.
 * 
 */

 char *PJL_ENTER_str = "@PJL ENTER LANGUAGE = %s\n";
 char *PCL_EXIT_str = "\033E";

void Send_job()
{
	int len = 0, i, c, n, cnt, tempfd;
	char *s, *pgm = 0;
	int done = 0;
	OBJ *l = 0, *match = 0, *files = 0;
	char *language;
	int langhpgl2 = 0; /*added by Samuel Lown @ CERN */
	char *file_util;
	struct stat statb;
	int progress_pc, progress_k;
	double total_size, progress_last, progress_total;
	char file_result[SMALLBUFFER];
	char errmsg[SMALLBUFFER];
	int error_count = 0;

	l = NEW_OBJ(l,MEMINFO);
	match = NEW_OBJ(match,MEMINFO);
	files = NEW_OBJ(files,MEMINFO);

	LOGMSG(LOG_INFO) _("Send_job: starting transfer") );

	Make_stdin_file();
	file_util = GET_HASH_STR_OBJ( Model, "file_util_path", MEMINFO );
	file_util = Fix_option_str( file_util, 0, 1, 1 );

	/* find if there are user specified langagues */
	language = 0;
 	if( !Force_processing && (Loweropts['c'-'a'] || Autodetect) ){
		language = RAW;
	} else if( (s = GET_HASH_STR_OBJ(Zopts,"language",MEMINFO)) && !ISNULL(s) ){
		language = s;
	}

 again:

	DEBUG2( "Send_job: foomatic %d", Foomatic );
	if( pgm ) SAFEFREE(pgm); pgm = 0;
	Init_outbuf();
	if( lseek(0,0,SEEK_SET) == -1 ){
		Errorcode = JABORT;
		LOGERR_DIE(LOGINFO)_("Send_job: lseek failed"));
	}
	/* we only need the first couple of characters */
	cnt = 64;
	if( cnt > Outmax ) cnt = Outmax; 
	DEBUG2( "Send_job: want %d", cnt );
	Outlen = read( 0, Outbuf, cnt);
	Outbuf[Outlen] = 0;
	DEBUG2( "Send_job: read %d from stdin '%s'", Outlen, Outbuf );
	if( Outlen < 0 ){
		Errorcode = JFAIL;
		LOGERR_DIE(LOGINFO) _("Send_job: read error on stdin") );
	}
	if( Outlen == 0 ){
		LOGMSG(LOG_INFO) _("Send_job: zero length job file") );
		return;
	}
	if( lseek(0,0,SEEK_SET) == -1 ){
		Errorcode = JABORT;
		LOGERR_DIE(LOGINFO)_("Send_job: lseek failed"));
	}

	/* decide on the job type */

	SET_HASH_STR_OBJ( Model,"file_output","",MEMINFO);
	if( language ){
		/* we have the language already */
	} else if( Force_conversion ){
		if( ISNULL(file_util) ){
			Errorcode = JABORT;
			FATAL(LOGINFO) _("Send_job: missing file_util_path value"));
		}
		DEBUG2( "Send_job: file_util_path '%s'", file_util );
		Use_file_util(file_util, file_result, sizeof(file_result) );
		language = file_result;
	} else if( !strncmp( PJL_UEL_str, Outbuf, safestrlen(PJL_UEL_str)) ){
		language = PJL;
	} else if( Outbuf[0] == '\033' ){
		if ( !strncmp(Outbuf+1, "E\033%0B", 5) ) { 
			langhpgl2 = 1; 
			DEBUG2( "Send_job: HPGL/2 language detected" );
		}
		language = PCL;
	} else if( Outbuf[0] == '\004' && !strncmp(Outbuf+1, "%!", 2) ){
		language = PS;
	} else if( !strncmp(Outbuf, "%!", 2) ){
		language = PS;
	} else if( !ISNULL(file_util) ){
		DEBUG2( "Send_job: file_util_path '%s'", file_util );
		file_result[0] = 0;
		Use_file_util(file_util, file_result, sizeof(file_result) );
		if( !ISNULL(file_result) ) language = file_result;
	}
	if( ISNULL(language) ){
		/* defaults */
		language = GET_HASH_STR_OBJ(Model,"default_language",MEMINFO);
	}
	if( ISNULL(language) ) language = UNKNOWN;

	/* rewind the file and get ready for action */
	Init_outbuf();
	if( lseek(0,0,SEEK_SET) == -1 ){
		Errorcode = JABORT;
		LOGERR_DIE(LOGINFO)_("Send_job: lseek failed"));
	}
	if( fstat(0,&statb) == -1 ){
		Errorcode = JABORT;
		LOGERR_DIE(LOGINFO)_("Send_job: fstat3 failed"));
	}

	LOGMSG(LOG_INFO) _("Send_job: initial job type '%s', size %d"), language, (int)statb.st_size );
	if (langhpgl2) LOGMSG(LOG_INFO) _("Send_job: HPGL/2 flag set") );

	if( safestrcmp(language,RAW) 
		&& (s = GET_HASH_STR_OBJ(Model, "file_output_match",MEMINFO)) ){
		/* split the matches up */
		Clear_OBJ(l);
		Split_STR_OBJ( /*p*/l, /*nomod*/1, /*str*/s,
			/*type*/OBJ_T_LIST, /*linesep*/"\n", /*escape*/"\n",
			/*comment*/ 0, /*trim*/1, /*keysep*/"=",/*do_append*/',', /*lc*/1,
			/*flagvalues*/ 1, /*value_urlunesc*/0, MEMINFO );
		if(DEBUGL2)SHORT_DUMP_OBJ("Send_job: file_output_match", l );
		for( i = 0; i < LEN_LIST_OBJ(l); ++i ){
			char *s = GET_ENTRY_LIST_OBJ(l,i);
			int c = 0;
			DEBUG2("Send_job: [%d] '%s'", i, s );
			if(s) while( (c = cval(s)) && (isspace(c) || c == ']' || c == '[') ) ++s;
			if( ISNULL(s) ) continue;
			Clear_OBJ(match);
			/* split the line into three parts */
			for( n = 0; !ISNULL(s) && n < 2; ++n ){
				char *t;
				while(isspace(cval(s))) ++s;
				t = safestrpbrk(s,WHITESPACE);
				if( t ){ c = *t; *t = 0; }
				if( cval(s) ){
					APPEND_LIST_OBJ(match,s,MEMINFO);
				}
				if( t ) { *t = c; ++t; }
				s = t;
			}
			if(s) while(isspace(cval(s))) ++s;
			if( !ISNULL(s) ){
				APPEND_LIST_OBJ(match,s,MEMINFO);
			}
			if(DEBUGL2)SHORT_DUMP_OBJ("Send_job: checking against", match );
			n = LEN_LIST_OBJ(match);
			if( n == 0 ) continue;
			if( n != 2 && n != 3 ){
				LOGMSG(LOG_INFO) _("Send_job: wrong number of fields in 'file_output_match' - '%s'"), s );
				continue;
			}
			{ char *t;
			t = GET_ENTRY_LIST_OBJ(match,0);
			if( !Globmatch( t, language ) ){
				t = GET_ENTRY_LIST_OBJ(match,1);
				SNPRINTF(file_result,sizeof(file_result)) "%s", t);
				language = file_result;
				DEBUG1("Send_job: match language '%s'", language, pgm );
				if( LEN_LIST_OBJ(match) == 3 ){
					t = GET_ENTRY_LIST_OBJ(match,2);
					pgm = Fix_option_str( t, 0, 1, 1 );
					DEBUG1("Send_job: converter pgm '%s', final '%s'", t, pgm );
				}
				break;
			}
			}
		}
	}

	language = Set_mode_lang(language);
	DEBUG1("Send_job: language '%s', converter pgm '%s'", language, pgm );
	LOGMSG(LOG_INFO) _("Send_job: decoded job type '%s'"), language );

	/* we can treat text as PCL */
	if( !safestrcasecmp(language,RAW) ){
		;
	} else if( !safestrcasecmp(language,MSG) ){
		;
	} else if( !safestrcasecmp(language,PJL) ){
		if( !Pjl ){
			Errorcode = JABORT;
			FATAL(LOGINFO)_("Send_job: job is PJL and PJL not supported"));
		}
	} else if( !safestrcasecmp(language,PCL) ){
		if(langhpgl2 && !Pcl){
			Errorcode = JABORT;
			FATAL(LOGINFO)_("Send_job: job is HPGL2 and neither PCL nor HPGL2 are supported"));
		} else if(!Pcl ){
			Errorcode = JABORT;
			FATAL(LOGINFO)_("Send_job: job is PCL and PCL not supported"));
		} else if(langhpgl2 && !Hpgl2) {
			/* reset langhpgl2 flag if HPGL2 not supported */
			langhpgl2 = 0;
			LOGMSG(LOG_INFO) _("Send_job: HPGL2 not supported, resorting to PCL") );
		}
	} else if( !safestrcasecmp(language,PS) ){
		if( !Ps ){
			Errorcode = JABORT;
			FATAL(LOGINFO)_("Send_job: job is PostScript and PostScript not supported"));
		}
		/*
		 * we use the Foomatic filter
		 */
		DEBUG1("Send_job: language '%s', foomatic %d, foomatic_rip %s, ppd %s",
			language, Foomatic, Foomatic_rip, Ppd_file );
		if( Foomatic ){
			Foomatic = 0;
			if( ISNULL(Foomatic_rip) ){
				LOGMSG(LOG_INFO) _("Send_job: foomatic enabled and no path to foomatic-rip") );
			} else if( ISNULL(Ppd_file ) ){
				LOGMSG(LOG_INFO) _("Send_job: foomatic enabled and no PPD file specified") );
			} else {
				if( pgm ) SAFEFREE(pgm); pgm = 0;
				pgm = Fix_option_str( Foomatic_rip, 0, 1, 1 );
				language = FILTER;
			}
		}
	} else if( !safestrcasecmp(language,TEXT) ){
		if( !Pcl && !Text ){
			Errorcode = JABORT;
			FATAL(LOGINFO)_("Send_job: job is Text and Text not supported"));
		}
		if( Pcl ) language = PCL;
	} else if( !safestrcasecmp(language,FILTER) ){
		if( ISNULL(pgm) ){
			Errorcode = JABORT;
			FATAL(LOGINFO)_("Send_job: filter wanted and no filter specified"));
		}
	} else {
		LOGMSG(LOG_INFO) _("Send_job: cannot process language '%s'"), language );
		goto done;
	}

	/* we now invoke the converter or do hanky-panky with the MSG
	 * this is called a hack of the 2nd order... Patrick
	 */
	if( !ISNULL( pgm ) ){
		LOGMSG(LOG_INFO) _("Send_job: job type '%s', converter '%s'"),
			language, pgm );
		if( lseek(0,0,SEEK_SET) == -1 ){
			Errorcode = JABORT;
			LOGERR_DIE(LOGINFO)_("Send_job: lseek failed"));
		}
		tempfd = Make_tempfile();
		/* here is where we do the dirty work */
		if( !safestrcasecmp( language, MSG ) ){
			/* now we cunningly force a rescan
			 * gahhh... I put this in but I hoped I would never
			 * have to use it
			 */
			Write_error_msg( tempfd, pgm );
			language = FILTER;
		} else {
			n = Filter_file( pgm, _("CONVERTER"), 0, tempfd, errmsg, sizeof(errmsg) );
			if( n ){
				close(tempfd);
				tempfd = Make_tempfile();
				/* stop recursion from causing problems */
				if( error_count > 2 ) {
					Errorcode = n;
					FATAL(LOGINFO)_("Send_job: converter failed, exit code %d"), n);
				}
				++error_count;
				SET_HASH_STR_OBJ( Model,"pgm",pgm,MEMINFO);
				SET_HASH_STR_OBJ( Model,"msg",errmsg,MEMINFO);
				Write_error_msg( tempfd, "conversion_error" );
				language = FILTER;
			}
		}
		if( fstat(tempfd,&statb) == -1 ){
			Errorcode = JABORT;
			LOGERR_DIE(LOGINFO)_("Send_job: fstat2 failed"));
		}
		LOGMSG(LOG_INFO)_("Send_job: converter done, output %ld bytes"),
			(long)(statb.st_size) );
		if( statb.st_size == 0 ){
			Errorcode = JABORT;
			LOGMSG(LOG_INFO)_("Send_job: zero length conversion output"));
		}
		if( tempfd != 0 ){
			if( dup2(tempfd,0) == -1 ){
				Errorcode = JABORT;
				LOGERR_DIE(LOGINFO)_("Send_job: dup2 failed"));
			}
			if( close(tempfd) == -1 ){
				Errorcode = JABORT;
				LOGERR_DIE(LOGINFO)_("Send_job: close(tempfd %d) failed"), tempfd);
			}
		}
		/* if you did filtering,  then reopen for the job */
		if( pgm ) SAFEFREE(pgm); pgm = 0;
	} else if( !safestrcmp(language,PJL) && Remove_pjl_at_start ){
		LOGMSG(LOG_INFO) _("Send_job: job type '%s', stripping leading PJL "), language  );
		if( lseek(0,0,SEEK_SET) == -1 ){
			Errorcode = JABORT;
			LOGERR_DIE(LOGINFO)_("Send_job: lseek failed"));
		}
		tempfd = Make_tempfile();
		Init_outbuf();
		do{
			len = read( 0, Outbuf+Outlen, Outmax - Outlen);
			DEBUG2( "Send_job: read %d from stdin", len );
			if( len < 0 ){
				Errorcode = JFAIL;
				LOGERR_DIE(LOGINFO) _("Send_job: read error on stdin") );
			}
			Outlen += len;
			Outbuf[Outlen] = 0;
			done = 0;
			while(!done){
				n = safestrlen(PJL_UEL_str);
				if( !strncmp( PJL_UEL_str, Outbuf, n) ){
					memmove( Outbuf, Outbuf+n, Outlen - n + 1);
					Outlen -= n;
					continue;
				} else if( !strncmp(Outbuf,"@PJL",4) ){
					if( (s = safestrpbrk(Outbuf,"\n")) ){
						*s++ = 0;
						n = s - Outbuf;
						memmove( Outbuf, s, Outlen - n + 1);
						Outlen -= n;
						continue;
					} else {
						break;
					}
				}
				done = 1;
			}
		} while( !done && len > 0 );
		/* now we write this to the output file */
		/*DEBUG2("Send_job: initial part len %d, '%s'", Outlen, Outbuf ); */
		do{
			if( Outlen > 0 ){
				len = write( tempfd, Outbuf, Outlen );
				if( len != Outlen ){
					Errorcode = JFAIL;
					LOGERR_DIE(LOGINFO) _("Send_job: write error to tempfile") );
				}
			}
			Outlen = read( 0, Outbuf, Outmax );
			Outbuf[Outlen] = 0;
			/* DEBUG2("Send_job: next part len %d, '%s'", Outlen, Outbuf ); */
		} while( Outlen > 0 );
		if( dup2(tempfd,0) == -1 ){
			Errorcode = JABORT;
			LOGERR_DIE(LOGINFO) _("Make_stdin_file: dup2 failed") );
		}
		if( tempfd != 0 ) close(tempfd); tempfd = -1;
		language = 0;
		/* if you did filtering,  then reopen for the job */
		goto again;
	} else {
		LOGMSG(LOG_INFO) _("Send_job: job type '%s'"), language  );
	}

	/* run the job through the converter */
	if( !safestrcmp(language,FILTER) ){
		language = 0;
		goto again;
	}

	/*
	 * If we have a HPGL2 job, rename language for Init_job
	 * Init_job will put the language back for us
	 */
	if ( langhpgl2 ) {
		language = HPGL2;
	}
	
	Init_outbuf();
	Init_job( language ); /* INIT JOB */
	Init_outbuf();

	DEBUG1("Send_job: doing file transfer");
	if( fstat( 0, &statb ) < 0 ){
		Errorcode = JABORT;
		LOGERR_DIE(LOGINFO) _("Send_job: cannot fstat fd 0") );
	}
	total_size = statb.st_size;
	if( lseek(0,0,SEEK_SET) == -1 ){
		Errorcode = JABORT;
		LOGERR_DIE(LOGINFO)_("Send_job: lseek failed"));
	}

	progress_last = progress_total = 0;
	progress_pc = progress_k = 0;
	if( (s = GET_HASH_STR_OBJ(Model,"progress_pc",MEMINFO)) ){
		progress_pc = atoi(s);
	}
	if( (s = GET_HASH_STR_OBJ(Model,"progress_k",MEMINFO)) ){
		progress_k = atoi(s);
	}
	if( progress_pc < 0 || progress_pc > 100 ){
		progress_pc = 0;
	}
	if( progress_k <= 0 ) progress_k = 0;
	if( progress_pc == 0 && progress_k == 0 ) progress_k = 100;
	DEBUG2( "Send_job: input stat 0%06o, size %0.0f, progress_pc %d, progress_k %d",
		(int)(statb.st_mode & S_IFMT),
		total_size, progress_pc, progress_k );
	LOGMSG(LOG_INFO) _("Send_job: transferring %0.0f bytes"), total_size );

	if( !safestrcmp(language,PS) && Remove_ctrl ){
		for( i = 0; i < safestrlen(Remove_ctrl); ++i ){
			Remove_ctrl[i] &= 0x1F;
		}
	}

	Init_outbuf();

	/* just a couple of bytes at the start */
	Outlen = read( 0, Outbuf, 8);
	Outbuf[Outlen] = 0;
	DEBUG2( "Send_job: read %d from stdin", Outlen );
	if( Outlen < 0 ){
		Errorcode = JFAIL;
		LOGERR_DIE(LOGINFO) _("Send_job: read error on stdin") );
	}
	if( Outlen == 0 ){
		LOGMSG(LOG_INFO) _("Send_job: zero length job file") );
		goto done;
	}

	/* strip off the ^D at start of PostScript jobs */
	if( !safestrcmp(language,PS) && Outbuf[0] == '\004' ){
		memmove(Outbuf,Outbuf+1,Outlen-1);
		Outlen -= 1;
	}
	if( Outlen > 1 && !safestrcmp(language,PCL) && Outbuf[0] == '\033' && Outbuf[1] == 'E' ){
		memmove(Outbuf,Outbuf+2,Outlen-2);
		Outlen -= 2;
	}
	Outbuf[Outlen] = 0;
	DEBUG2( "Send_job: Outbuf after type and stripping '%s'", Outbuf );

	done = 0;
	do{
		len = 0;
		progress_total += Outlen;
		if( !safestrcmp(language,PS) && Tbcp ){
			DEBUG4("Send_job: tbcp");
			for( cnt = 0; cnt < Outlen; ){
				len = 0; c = 0;
				for( i = cnt; c == 0 && i < Outlen; ){
					switch( (c = ((unsigned char *)Outbuf)[i]) ){
					case 0x01: case 0x03: case 0x04: case 0x05:
					case 0x11: case 0x13: case 0x14: case 0x1C:
						break;
					default: c = 0; ++i;
						break;
					}
				}
				/* we now write the string from cnt to count+i-1 */
				n = i - cnt;
				DEBUG1("Send_job: tbcp writing %d", n );
				if( (len = Write_read_timeout( n, Outbuf+cnt, Send_job_rw_timeout )) ){
					break;
				}
				if( c ){
					char b[2];
					DEBUG1("Send_job: tbcp escape 0x%02x", c );
					b[0] = '\001'; b[1] = c ^ 0x40;
					if( (len = Write_read_timeout( 2, b, Send_job_rw_timeout )) ){
						break;
					}
					n += 1;
				}
				cnt += n;
			}
		} else if( !safestrcmp(language,PS) && Remove_ctrl ){
			for( i = 0; i < Outlen; ){
				c = cval(Outbuf+i);
				if( safestrchr( Remove_ctrl,c ) ){
					memmove( Outbuf+i, Outbuf+i+1,Outlen-i);
					--Outlen;
				} else {
					++i;
				}
			}
			len = Write_read_timeout( Outlen, Outbuf, Send_job_rw_timeout );
		} else if(
			(!safestrcmp(language,TEXT) || !safestrcmp(language,PCL))
				&& Crlf ){
			DEBUG4("Send_job: crlf");
			for( cnt = 0; cnt < Outlen; ){
				len = 0; c = 0;
				for( i = cnt; c == 0 && i < Outlen; ){
					switch( (c = ((unsigned char *)Outbuf)[i]) ){
					case '\n':
						break;
					default: c = 0; ++i;
						break;
					}
				}
				/* we now write the string from cnt to count+i-1 */
				n = i - cnt;
				DEBUG1("Send_job: crlf writing %d", n );
				if( (len = Write_read_timeout( n, Outbuf+cnt, Send_job_rw_timeout )) ){
					break;
				}
				if( c ){
					if( (len = Write_read_timeout( 2, "\r\n", Send_job_rw_timeout )) ){
						break;
					}
					n += 1;
				}
				cnt += n;
			}
		} else {
			DEBUG1("Send_job: writing %d", Outlen );
			len = Write_read_timeout( Outlen, Outbuf, Send_job_rw_timeout );
		}
		if( len ){
			Errorcode = JFAIL;
			LOGERR_DIE(LOGINFO) _("Send_job: job failed during copy") );
		}
		Init_outbuf();
		DEBUG1("Send_job: total written %0.0f", progress_total );
		/*
		 * now we do progress report
		 */
		i = (progress_total - progress_last)/total_size*100;
		if( progress_pc && (i >= progress_pc) ){
			DEBUG1("Send_job: pc total %0.0f, change %d%%", progress_total, i );
			i = progress_total/total_size*100;
			LOGMSG(LOG_INFO)_("Send_job: %d percent done"), i);
			progress_last = progress_total;
		} else if( progress_k ){
			i = (progress_total - progress_last)/1024;
			DEBUG1("Send_job: k total %0.0f, change %d", progress_total, i );
			if( i >= progress_k ){
				if( progress_pc ){
					i = progress_total/total_size*100;
					LOGMSG(LOG_INFO)_("Send_job: %d percent done"), i);
				} else
					LOGMSG(LOG_INFO)_("Send_job: %d Kbytes done"), (int)(progress_total/1024));
				progress_last = progress_total;
			}
		}
		if( Outlen == 0 && !done ){
			while( Outlen < Outmax 
					&& (len = read( 0, Outbuf+Outlen, Outmax - Outlen )) > 0 ){
				DEBUG1("Send_job: read %d", len );
				Outlen += len;
			}
			Outbuf[Outlen] = 0;
			DEBUG1("Send_job: read total %d", Outlen );
			if( len < 0 ){
				Errorcode = JFAIL;
				LOGERR_DIE(LOGINFO) _("Send_job: read error on stdin") );
			} else if( len == 0 ){
				done = 1;
			}
		}
	} while( Outlen > 0 );
	DEBUG1( "Send_job: finished file transfer" );

	Term_job( language );

 done:
	FREE_OBJ(l); l = 0;
	FREE_OBJ(match); match = 0;
	FREE_OBJ(files); files = 0;
}

void URL_decode( char *s )
{
	if( s ) while( (s = safestrpbrk(s, "%?")) ){
		if( cval(s) == '?' ){
			*s = ' ';
		} if( (s[0] = s[1]) && (s[1] = s[2]) ){
			s[2] = 0;
			s[0] = strtol(s,0,16);
			memmove(s+1,s+3, safestrlen(s+3)+1);
			++s;
		} else {
			break;
		}
	}
}

/*
 * Builtins
 */

 struct keyvalue Builtin_values[] = {
/* we get the various UEL strings */
{"Font_download","font_download",(void *)Font_download,0,0},
{0,0,0,0,0}
};


/*
 * Process_OF_mode()
 *  We read from STDIN on a character by character basis.
 *  We then send the output to STDOUT until we are done
 *  If we get a STOP sequence, we suspend ourselves.
 *
 *  We actually have to wait on 3 FD's -
 *  0 to see if we have input
 *  1 to see if we have output
 *
 *  We will put the output in the Outbuf buffer
 *  Note:  if we need to suspend,  we do this AFTER we have
 *   written the output
 */

 static char stop[] = "\031\001";    /* sent to cause filter to suspend */

int Process_OF_mode(void)
{
	char inbuf[2];
	int n, state;
	n = state = 0;
	if( ftruncate( 1, 0 ) == -1 ){
		Errorcode = JABORT;
		LOGERR_DIE(LOGINFO)_("Process_OF_mode: ftruncate fd 1 failed"));
	}
	while( (n = read(0,inbuf,1)) > 0 ){
		if( inbuf[0] == stop[state] ){
			++state;
			if( stop[state] == 0 ){
				return(1);
			}
			continue;
		} else if( state ){
			state = 0;
			if( write(1,stop,1) != 1 ){
				Errorcode = JABORT;
				LOGERR_DIE(LOGINFO)_("Process_OF_mode: write failed"));
			}
		}
		if( write(1,inbuf,1) != 1 ){
			Errorcode = JABORT;
			LOGERR_DIE(LOGINFO)_("Process_OF_mode: write failed"));
		}
	}
	if( state && write(1,stop,1) != 1 ){
		Errorcode = JABORT;
		LOGERR_DIE(LOGINFO)_("Process_OF_mode: write failed"));
	}
	return(n);
}

/*
 * set the close on exec flag for a reasonable range of FD's
 */
void close_on_exec( int n )
{
    int fd, max = Max_fd+10;

    for( fd = n; fd < max; fd++ ){
		fcntl(fd, F_SETFD, 1);
	}
}
void Set_max_fd( int n )
{
	if( Max_fd < n ) Max_fd = n;
}

/*
 * Use_file_util(char *pgm, char *matches, OBJ *args
 * 
 * We will need to use the file utility
 *  pgm = program to invoke
 *  matches = lines in form of 
 *      glob   returntype  program
 *  args = arguments to use if successful
 * RETURN 0 if no match
 *        != 0 if match
 */

void Use_file_util(char *pgm, char *value, int value_len )
{
	int n, fd, c;
	char *s, *t;

	DEBUG1("Use_file_util: pgm '%s', value_len %d", pgm, value_len);
	LOGMSG(LOG_INFO)_("Use_file_util: file program = '%s'"), pgm );

	value[0] = 0;
	if( lseek(0,0,SEEK_SET) == -1 ){
		Errorcode = JABORT;
		LOGERR_DIE(LOGINFO)_("Use_file_util: lseek failed"));
	}
	fd = Make_tempfile();
	n = Filter_file( pgm, _("FILE_UTIL"), 0, fd, 0, 0 );
	if( n ){
		Errorcode = n;
		LOGERR_DIE(LOGINFO)_("Use_file_util: exit code %d"), n );
	}
	if( lseek(fd,0,SEEK_SET) == -1 ){
		Errorcode = JABORT;
		LOGERR_DIE(LOGINFO)_("Use_file_util: lseek failed"));
	}
	if( (n = read(fd,value,value_len-1)) < 0){
		Errorcode = JABORT;
		LOGERR_DIE(LOGINFO)_("Use_file_util: read failed"));
	}
	if( n >= 0 ) value[n] = 0;
	close(fd);
	DEBUG1("Use_file_util: read %d, '%s'", n, value );
	lowercase(value);
	if( (s = safestrchr(value,':')) ){
		++s;
	} else {
		s = value;
	}
	while( isspace(cval(s)) ) ++s;
	for( t = value; (c = cval(s)) ; ++s ){
		if( isspace(cval(s)) && (isspace(cval(s+1)) || !cval(s+1)) ){
			;
		} else if( isspace(cval(s)) ){
			*t++ = '_';
		} else {
			*t++ = *s;
		}
	}
	*t = 0;
	LOGMSG(LOG_INFO)_("Use_file_util: file information = '%s'"), value );
	DEBUG4("Use_file_util: file util done, '%s'", value );
	SET_HASH_STR_OBJ( Model,"file_output",value,MEMINFO);
}

int Make_tempfile( void )
{
	int fd;
	char buffer[MAXPATHLEN];
	char *tempfile;

	tempfile = GET_HASH_STR_OBJ( Model,"tempfile", MEMINFO );
	if( ISNULL(tempfile) ) tempfile = getenv("TMP");
	SNPRINTF(buffer,sizeof(buffer)) "%s%sifhpXXXXXX",
		ISNULL(tempfile)?"":tempfile, ISNULL(tempfile)?"":"/" );
	DEBUG1( "Make_tempfile: tempfile '%s'", buffer );
	if( (fd = mkstemp(buffer)) == -1 ){
		Errorcode = JABORT;
		LOGERR_DIE(LOGINFO)"Make_tempfile: mkstemp failed with '%s'", buffer);
	}
	DEBUG1( "Make_tempfile: new tempfile '%s', fd %d", buffer, fd );
	unlink(buffer);
	return( fd );
}

void Make_stdin_file()
{
	int is_file, fd, in, n, i;
	struct stat statb;
	char *s;

	if( fstat( 0, &statb ) < 0 ){
		Errorcode = JABORT;
		LOGERR_DIE(LOGINFO) _("Make_stdin_file: cannot fstat fd 1") );
	}
	is_file = ((statb.st_mode & S_IFMT) == S_IFREG);
	DEBUG2( "Make_stdin_file: input is_file %d, size %d",
		is_file, (int)statb.st_size );
	if( !is_file ){
		fd = Make_tempfile();
		in = 1;
		do{
			n = 0;
			for( i = Outlen, s = Outbuf;
				i > 0 && (n = write(fd,s,i)) > 0;
				i -= n, s += n);
			if( n < 0 ){
				Errorcode = JABORT;
				LOGERR_DIE(LOGINFO) _("Make_stdin_file: write to tempfile fd %d failed"), fd );
			}
			Outlen = 0;
			if( in > 0 ){
				n = 0;
				for( Outlen = 0;
					Outlen < Outmax
					&& (n = read(0,Outbuf+Outlen,Outmax-Outlen)) > 0;
					Outlen+=n);
				if( n < 0 ){
					Errorcode = JABORT;
					LOGERR_DIE(LOGINFO) _("Make_stdin_file: read from stdin") );
				} else if( n == 0 ){
					in = 0;
				}
			}
		} while( Outlen > 0 );
		if( dup2(fd,0) == -1 ){
			Errorcode = JABORT;
			LOGERR_DIE(LOGINFO) _("Make_stdin_file: dup2 failed") );
		}
		if( fd != 0 ) close(fd); fd = -1;
	}
	if( lseek(0,0,SEEK_SET) == -1 ){
		Errorcode = JABORT;
		LOGERR_DIE(LOGINFO)_("Make_stdin_file: lseek failed"));
	}
	Outlen = 0;
}

char * Set_mode_lang( char *s )
{
	if( !ISNULL(s) ){
		if(!strcasecmp( s, PS )){ s = PS; }
		else if(!strcasecmp( s, "ps" )){ s = PS; }
		else if(!strcasecmp( s, "postscript" )){ s = PS; }
		else if(!strcasecmp( s, PCL )){ s = PCL; }
		else if(!strcasecmp( s, HPGL2 )){ s = HPGL2; }
		else if(!strcasecmp( s, PJL )){ s = PJL; }
		else if(!strcasecmp( s, TEXT )){ s = TEXT; }
		else if(!strcasecmp( s, RAW )){ s = RAW; }
		else if(!strcasecmp( s, FILTER )){ s = FILTER; }
	}
	return s;
}

/* Fd_readable - check to see if you can read status from the file descriptor
 * use fcntl and stat to make sure that you do not have
 *   a file as the device and that it is open RDWR
 */

int Fd_readable( int fd, int *poll_for_status )
{
	int n, readable = 0, err = 0;
	struct stat statb;
	char buffer[1];


	if( fstat(fd,&statb) == -1 ){
		Errorcode = JABORT;
		FATAL(LOGINFO) _("ifhp: fd is not open"),fd);
	}
	if( S_ISREG( statb.st_mode ) ){
		readable = 0;
		return( readable );
	}
	Set_nonblock_io( fd );
	errno = 0;
	n = read( fd, buffer, 1 );
	err = errno;
	Set_block_io( fd );
	DEBUG1("Fd_readable: nonblocking read returned %d, errno %d (%s)", n, errno, strerror(errno)   );
	if( n >= 0 || ( 0
#if defined(EWOULDBLOCK)
					|| err == EWOULDBLOCK
#endif
#if defined(EAGAIN)
					|| err == EAGAIN
#endif
			) ){
		if( n > 0 ) Peek_char = cval(buffer);
		readable = 1;
		if( !isatty( fd ) ){
			if( fstat( fd, &statb ) < 0 ){
				Errorcode = JABORT;
				LOGERR_DIE(LOGINFO) _("Fd_readable: cannot fstat fd %d"), fd );
			}
			DEBUG2( "Fd_readable: fd %d st_mode 0%o", fd, statb.st_mode & S_IFMT );
			if ( S_ISCHR(statb.st_mode ) ){
				*poll_for_status = 1;
			}
		}
	}
	return( readable );
}

void Init_job( char *language )
{
	char buffer[SMALLBUFFER];
	OBJ *l = 0;
	int len, i;
	char *s;

	l = NEW_OBJ(l,MEMINFO);

	DEBUG2("Init_job: language '%s'", language );
	if( !safestrcmp(language,TEXT) ) language = PCL;
	if( Pjl && Pjl_enter && (!safestrcmp(language,PS) || !safestrcmp(language,PCL) || !safestrcmp(language,HPGL2)) ){
		SNPRINTF(buffer, sizeof(buffer)) 
			PJL_ENTER_str, language );
		Put_pjl( buffer );
		memset( buffer, 0, sizeof(buffer) );
		/* HPGL2 is subset of PCL, set language back 
		 * to PCL now that the PJL language type is set
		 */
		if (!safestrcmp(language,HPGL2)) language = PCL;
		/* 
		 * force nulls to be output
		 */
		for( i = Null_pad_count; i > 0; i -= len){
			len = sizeof(buffer);
			if( i < len ) len = i;
			Put_outbuf_len( buffer, len );
		}
	}
	DEBUG2("Init_job: after PJL ENTER '%s'", Outbuf );

	/* now we handle the various initializations */
	if( Pcl && !safestrcmp(language,PCL) ){
		DEBUG1("Init_job: doing pcl init");
		if( Pcl_eoj_at_start ) Put_outbuf_str( PCL_EXIT_str );
		if( (s = GET_HASH_STR_OBJ( Model, "pcl_init", MEMINFO)) ){
			DEBUG1("Init_job: 'pcl_init'='%s'", s);
			Clear_OBJ(l);
			Split_STR_OBJ( /*p*/l, /*nomod*/1, /*str*/s,
				/*type*/OBJ_T_LIST, /*linesep*/LISTSEP WHITESPACE, /*escape*/LISTSEP WHITESPACE,
				/*comment*/ 0, /*trim*/1, /*keysep*/"=",/*do_append*/',', /*lc*/1,
				/*flagvalues*/ 1, /*value_urlunesc*/0, MEMINFO );
			Resolve_list( "pcl_", l, 0, Put_pcl, 0, MAX_DEPTH );
			Clear_OBJ(l);
		}
		DEBUG1("Init_job: 'pcl' and Topts");
		Resolve_user_opts( "pcl_", Pcl_user_opts, Unsorted_Topts, Model, Put_pcl );
		DEBUG1("Init_job: 'pcl' and Zopts");
		Resolve_user_opts( "pcl_", Pcl_user_opts, Unsorted_Zopts, Zopts, Put_pcl );
	} else if( Ps && !safestrcmp(language,PS) ){
		DEBUG1("Init_job: doing ps init");
		if( !Appsocket && Ps_eoj && Ps_eoj_at_start ) Put_outbuf_str( CTRL_D );
		if( Tbcp ){
			DEBUG2("Init_job: doing TBCP");
			Put_outbuf_str("\001M");
		}
		if( (s = GET_HASH_STR_OBJ(Model, "ps_level_str",MEMINFO)) ){
			Put_ps(s);
		} else {
			Put_ps("%!");
		}
		if( (s  = GET_HASH_STR_OBJ( Model, "ps_init", MEMINFO)) ){
			DEBUG1("Init_job: 'ps_init'='%s'", s);
			Clear_OBJ(l);
			Split_STR_OBJ( /*p*/l, /*nomod*/1, /*str*/s,
				/*type*/OBJ_T_LIST, /*linesep*/LISTSEP WHITESPACE, /*escape*/LISTSEP WHITESPACE,
				/*comment*/ 0, /*trim*/1, /*keysep*/"=",/*do_append*/',', /*lc*/1,
				/*flagvalues*/ 1, /*value_urlunesc*/0, MEMINFO );
			Resolve_list( "ps_", l, 0, Put_ps, 0, MAX_DEPTH );
			Clear_OBJ(l);
		}
		DEBUG1("Init_job: 'ps' and Topts");
		Resolve_user_opts( "ps_", Ps_user_opts, Unsorted_Topts, Model, Put_ps );
		DEBUG1("Init_job: 'ps' and Zopts");
		Resolve_user_opts( "ps_", Ps_user_opts, Unsorted_Zopts, Zopts, Put_ps );
	}
	DEBUG2("Init_job: final '%s'", Outbuf );
	len = Write_read_timeout( Outlen, Outbuf, Send_job_rw_timeout );
	if( len ){
		Errorcode = JFAIL;
		FATAL(LOGINFO) "Init_job: job timed out language selection" );
	}
	Init_outbuf();
	FREE_OBJ(l); l = 0;
}

void Term_job( char *language )
{
	int len;
	OBJ *l = 0;
	char *s;

	DEBUG2("Term_job: language '%s'", language );

	l = NEW_OBJ(l,MEMINFO);
	/* now we handle the various terminations */
	if( Pcl && !safestrcmp(language,PCL) ){
		DEBUG1("Term_job: doing pcl term");
		if( (s = GET_HASH_STR_OBJ( Model, "pcl_term", MEMINFO)) ){
			DEBUG1("Term_job: 'pcl_term'='%s'", s);
			Clear_OBJ(l);
			Split_STR_OBJ( /*p*/l, /*nomod*/1, /*str*/s,
				/*type*/OBJ_T_LIST, /*linesep*/LISTSEP WHITESPACE, /*escape*/LISTSEP WHITESPACE,
				/*comment*/ 0, /*trim*/1, /*keysep*/"=",/*do_append*/',', /*lc*/1,
				/*flagvalues*/ 1, /*value_urlunesc*/0, MEMINFO );
			Resolve_list( "pcl_", l, 0, Put_pcl, 0, MAX_DEPTH );
			Clear_OBJ(l);
		}
	} else if( Ps && !safestrcmp(language,PS) ){
		DEBUG1("Term_job: doing ps term");
		if( (s = GET_HASH_STR_OBJ( Model, "ps_term", MEMINFO)) ){
			DEBUG1("Term_job: 'ps_term'='%s'", s);
			Clear_OBJ(l);
			Split_STR_OBJ( /*p*/l, /*nomod*/1, /*str*/s,
				/*type*/OBJ_T_LIST, /*linesep*/LISTSEP WHITESPACE, /*escape*/LISTSEP WHITESPACE,
				/*comment*/ 0, /*trim*/1, /*keysep*/"=",/*do_append*/',', /*lc*/1,
				/*flagvalues*/ 1, /*value_urlunesc*/0, MEMINFO );
			Resolve_list( "ps_", l, 0, Put_ps, 0, MAX_DEPTH );
			Clear_OBJ(l);
		}
	}
	if( Pcl && ( !safestrcmp(language,PCL) || !safestrcmp(language,TEXT) ) ){
		if( Pcl_eoj_at_end ) Put_outbuf_str( PCL_EXIT_str );
	}
	if( Ps && !safestrcmp(language,PS) ){
		if( Ps_eoj && Ps_eoj_at_end ) Put_outbuf_str( CTRL_D );
	}
	len = Write_read_timeout( Outlen, Outbuf, Send_job_rw_timeout );
	if( len ){
		Errorcode = JFAIL;
		FATAL(LOGINFO) _("Term_job: job error on cleanup strings") );
	}
	Init_outbuf();
	FREE_OBJ(l); l = 0;
}

/*
 * filter a file through a program
 */

int Filter_file( char *pgm, char *title, int fd_stdin, int fd_stdout,
	char *errbuffer, int len_errbuffer )
{
	char *s;
	OBJ *l = 0;
	int error_fd[2], pid, len, n, c;
	char buffer[SMALLBUFFER];
	plp_status_t status;
	char *orig_pgm = safestrdup(pgm,MEMINFO);

	pgm = orig_pgm;
	l = NEW_OBJ(l,MEMINFO);

	while( (c = cval(pgm)) == '|' || isspace(c)) ++pgm;

	if( safestrstr(pgm,"ZOPTS") || safestrstr(pgm,"TOPTS") ){
		LOGMSG(LOG_INFO)_("Filter_file: WARNING - 'ZOPTS' and 'TOPS' conversion options replaced by {Z} and {T} - '%s'"), pgm ); 
	}
	if( (s = safestrstr(pgm,"ARGV")) && s[4] != '}' ){
		LOGMSG(LOG_INFO)"Filter_file: WARNING - 'ARGV' conversion option replaced by {ARGV} - '%s'", pgm ); 
	}
	DEBUG4("Filter_file: pgm '%s', title '%s', stdin %d, stdout %d",
		pgm, title, fd_stdin, fd_stdout );
	if( pgm && (safestrpbrk( pgm, "|><;" ) || (cval(pgm) == '(')) ){
		APPEND_LIST_OBJ(l,"/bin/sh", MEMINFO);
		APPEND_LIST_OBJ(l,"-c", MEMINFO);
		if( cval(pgm) != '(' ){
			s = safestrdup3("( ",pgm," )", MEMINFO );
			APPEND_LIST_OBJ(l, s, MEMINFO );
			if(s) SAFEFREE(s); s = 0;
		} else {
			APPEND_LIST_OBJ(l, pgm, MEMINFO );
		}
	} else {
		Split_cmd_line_OBJ( l, pgm );
	}
	if(DEBUGL2)SHORT_DUMP_OBJ("Filter_file: process args", l );
	if( pipe(error_fd) == -1 ){
		Errorcode = JABORT;
		LOGERR_DIE(LOGINFO)_("Filter_file: pipe failed"));
	}
	Set_max_fd(error_fd[0]); Set_max_fd(error_fd[1]);

	/* fork the process */
	if( (pid = fork()) == -1 ){
		Errorcode = JABORT;
		LOGERR_DIE(LOGINFO)_("Filter_file: fork failed"));
	} else if( pid == 0 ){
		if( fd_stdin < 0 && (fd_stdin = open("/dev/null", O_RDWR)) < 0 ){
			Errorcode = JABORT;
			LOGERR_DIE(LOGINFO)_("Filter_file: open(/dev/null) failed"));
		}
		if( fd_stdout < 0 && (fd_stdout = open("/dev/null", O_RDWR)) < 0 ){
			Errorcode = JABORT;
			LOGERR_DIE(LOGINFO)_("Filter_file: open(/dev/null) failed"));
		}
		if( (fd_stdin != 0 && dup2(fd_stdin,0) == -1)
			|| (fd_stdout != 1 && dup2(fd_stdout,1) == -1)
			|| dup2(error_fd[1],2) == -1 ){
			Errorcode = JABORT;
			LOGERR_DIE(LOGINFO)_("Filter_file: dup2 failed"));
		}
		close_on_exec(3);
		/* now we exec the filter process */
		{
			char **argv = GET_LIST_OBJ(l);
			execve( argv[0], argv, Envp );
			/* ooops... error */
			SNPRINTF(buffer,sizeof(buffer))  _("execv '%s' failed - %s"), argv[0],
				Errormsg(errno) );
			Write_fd_str(2,buffer);
		}
		exit(JABORT);
	}

	close(error_fd[1]);
	SNPRINTF(buffer, sizeof(buffer))  "%s", pgm );
	if( (s = safestrpbrk( buffer, WHITESPACE )) ) *s = 0;
	if( (s = safestrrchr( buffer, '/' )) ){
		++s;
	} else {
		s = buffer;
	}
	LOGMSG(LOG_INFO)_("Filter_file: started %s- '%s'"), title, s);
	len = 0;
	buffer[0] = 0;
	if( errbuffer ) errbuffer[0] = 0;
	while( len < (int)sizeof(buffer)-1
		&& (n = read(error_fd[0],buffer+len,sizeof(buffer)-1-len)) > 0 ){
		buffer[n+len] = 0;
		while( (s = safestrchr(buffer,'\n')) ){
			*s++ = 0;
			LOGMSG(LOG_INFO)_("Filter_file: '%s' msg '%s'"), title, buffer );
			memmove(buffer,s,safestrlen(s)+1);
		}
		len = safestrlen(buffer);
	}
	if( len ){
		LOGMSG(LOG_INFO)_("Filter_file: '%s' msg '%s'"), title, buffer );
	}
	close(error_fd[0]);
	while( (n = waitpid(pid,&status,0)) != pid ){
		if( pid == -1 && errno == ECHILD ){
			Errorcode = JABORT;
			LOGERR_DIE(LOGINFO) _("ifhp: waitpid and no child!") );
		}
		DEBUG1("Filter_file: converter pid %d, exit '%s'", pid,
			Decode_status( &status ) );
	}
	DEBUG1("Filter_file: converter pid %d, exit '%s'", pid,
		Decode_status( &status ) );
	if( WIFEXITED(status) ){
		n = WEXITSTATUS(status);
		if( n > 0 && n < 32 ) n += JFAIL-1;
	} else if( WIFSIGNALED(status) ){
		n = WTERMSIG(status);
		FATAL(LOGINFO)_("Filter_file: converter process died with signal %d, '%s'"),
			n, Sigstr(n));
		n = JSIGNAL;
	}
	if( n && errbuffer && !errbuffer[0] ){
		SNPRINTF(errbuffer,len_errbuffer) "Filter_file: converter exited with status '%s'",
		Decode_status( &status ) );
	}
	if( orig_pgm ) SAFEFREE(orig_pgm); orig_pgm = 0;
	return( n );
}

/*
 * Write_error_msg( int fd, char *id )
 *  - we expand and find the values for writing error messages
 *  This error message will actually then be sent to the printer
 *  There are two formats for message:
 *   ps_${id} = ... \%{msg}
 *   pcl_${id} = ... \%{msg}
 *   ${id} = ... \%{msg}
 *  If PostScript available and ps_${id} then we use ps_${id}
 *  else if PCL available and pcl_${id} then we use pcl_${id}
 *  else we use ${id}
 */

void Write_error_msg( int fd, char *id )
{
	char *s;
	char name[SMALLBUFFER];
	/* now we find the string to use */
	s = 0;
	if( !s && Ps ){
		SNPRINTF(name,sizeof(name)) "ps_%s", id );
		 s = GET_HASH_STR_OBJ( Model, name, MEMINFO );
	}
	if( !s && Pcl ){
		SNPRINTF(name,sizeof(name)) "pcl_%s", id );
		 s = GET_HASH_STR_OBJ( Model, name, MEMINFO );
	}
	if( !s ){
		SNPRINTF(name,sizeof(name)) "%s", id );
		 s = GET_HASH_STR_OBJ( Model, name, MEMINFO );
	}
	if( s ){
		/* now we expand it */
		s = Fix_option_str( s, 0, 0, 0 );
		DEBUG3("Write_error_msg: '%s'", s );
		Write_fd_str( fd , s );
	}
}

/***************************************************************************
 * Split_cmd_line_OBJ
 *   if we have xx "yy zz" we split this as
 *  xx
 *  yy zz
 ***************************************************************************/

OBJ * Split_cmd_line_OBJ( OBJ *l, char *s )
{
	char *t, *u;
	int c;

	DEBUG3("Split_cmd_line_OBJ: line '%s'", s );
	while( s && (c = cval(s)) ){
		if( isspace(c) ){
			++s;
			continue;
		}
		if( c == '"' || c == '\'' ){
			/* we now have hit a string */
			++s;
			for( (t = safestrchr(s,c)); t && t > s && t[-1] == '\\'; t = safestrchr(t+1,c) );
		} else {
			t = strpbrk(s, WHITESPACE);
		}
		if( t ) *t++ = 0;
		for( u = s; (u = safestrchr(u,'\'')); ){
			if( u > s && u[-1] == '\\' ){
				memmove(u-1,u,safestrlen(u)+1);
			} else{
				++u;
			}
		}
		DEBUG5("Split_cmd_line: arg '%s', rest '%s'", s, t );
		l = APPEND_LIST_OBJ( l, s, MEMINFO);
		s = t;
	}
	if(DEBUGL3){ SHORT_DUMP_OBJ("Split_cmd_line_OBJ", l ); }
	return( l );
}

/*
 * we extract the values from the raw list of lines
 * dkey = dvalue 
 * [xx yy zz]
 * key = value
 *  ...
 * [ xx ]
 * key = value
 *
 *  Index:  'default' =>"default"
 *  Index:  'xx' =>"xx yy zz,xx"
 *  Index:  'yy' =>"xx yy zz"
 *  Index:  'zz' =>"xx yy zz"
 *   Values:  'default' => dkey = dvalue...
 *   Values:  'xx' => key = value...
 *   Values:  'xx yy zz' => key = value...
 */

void Make_model_index( OBJ *input, OBJ *index, OBJ *entries )
{
	/* we start off with the default */
	OBJ *current_entry = 0, *items = 0, *strval = 0;
	char *s, *t, *str;
	int line, linecount, i, c, tc;
	
	items = NEW_OBJ(items,MEMINFO);
	strval = SET_STR_OBJ(strval,"",MEMINFO);

	current_entry = Make_new_model_entry( "default", index, entries, items, strval );
	SET_STR_OBJ(strval,"",MEMINFO);

	linecount = LEN_LIST_OBJ(input);
	for( line = 0; line < linecount; ++line  ){
		s = GET_ENTRY_LIST_OBJ( input, line );
		/* remove trailing blanks */
		for( i = safestrlen(s); i > 0; --i ){
			if( !isspace( cval(s+i-1) ) ){
				s[i] = 0;
				break;
			}
		}
		/* ignore blank lines */
		if( ISNULL(s) ) continue;
		DEBUGF(DDB3)("Make_model_index: input line [%d] '%s'", line, s );
		c = cval(s);
		str = VAL_STR_OBJ( strval );
		if( isspace(c) || c == ']' ){
			if( cval(str) ){
				APPEND_STR_OBJ(MEMINFO,strval,"\n",s,0);
			} else {
				while( (c = cval(s)) && isspace(c) ) ++s;
				if( c ){
					Errorcode = JABORT;
					FATAL(LOG_INFO) "Make_model_index: missing key name for value '%s'", s );
				}
			}
		} else {
			if( cval(str) ){
				APPEND_STR_OBJ( MEMINFO, current_entry, str, "\001", 0 );
				DEBUGF(DDB3)("Make_model_index: appended value '%s'", VAL_STR_OBJ(current_entry) );
			}
			/* now we check for '[...' header */
			if( c == '[' ){
				current_entry = Make_new_model_entry( s, index, entries, items, strval );
				SET_STR_OBJ(strval,"",MEMINFO);
				continue;
			}
			/* we need to find if we have a tc= */
			SET_STR_OBJ(strval,s,MEMINFO);
			s = VAL_STR_OBJ(strval);
			if( (t = safestrpbrk(s,VALUESEP)) ){
				c = *t;
				*t = 0;
				tc = !safestrcmp("tc",s );
				*t = c;
				if( tc ){
					t = safestrchr(t,'=');
					if( !t ){
						Errorcode = JABORT;
						FATAL(LOG_INFO) "Make_model_index: missing tc value '%s'", s );
					}
					if( t[-1] != '+' ){
						int len = safestrlen(s);
						APPEND_STR_OBJ(MEMINFO,strval," ",0);
						s = VAL_STR_OBJ(strval);
						s[len] = 0;
						t = safestrchr(t,'=');
						memmove(t+1,t,safestrlen(t)+1);
						t[0] = '+';
					}
				}
			}
		}
		DEBUGF(DDB3)("Make_model_index: line [%d] strval '%s'", line,VAL_STR_OBJ(strval) );
	}
	str = VAL_STR_OBJ(strval);
	if( cval(str) ){
		APPEND_STR_OBJ( MEMINFO, current_entry, str, "\001", 0 );
		DEBUGF(DDB2)("Make_model_index: setting '%s'", VAL_STR_OBJ(current_entry) );
	}
	DEBUGFC(DDB1){
		SHORT_DUMP_OBJ("Make_model_index: index", index );
		SHORT_DUMP_OBJ("Make_model_index: entries", entries );
	}
	FREE_OBJ(items); items = 0;
	FREE_OBJ(strval); strval = 0;
}

OBJ *Make_new_model_entry( char *key, OBJ *index, OBJ *entries,
	OBJ *items, OBJ *strval )
{
	OBJ *current_entry, *p;
	char *newkey, *s;
	int i;

	DEBUGF(DDB1)("Make_new_model_entry: new key '%s'", key );
	Clear_OBJ(items);
	Split_STR_OBJ( /*p*/items, /*nomod*/1, /*str*/key,
		/*type*/OBJ_T_LIST, /*linesep*/"[]" WHITESPACE, /*escape*/WHITESPACE,
		/*comment*/ 0, /*trim*/1, /*keysep*/"=",/*do_append*/',', /*lc*/1,
		/*flagvalues*/ 1, /*value_urlunesc*/0, MEMINFO );
	DEBUGFC(DDB3){ SHORT_DUMP_OBJ("Make_new_model_entry: items", items); }
	strval = SET_STR_OBJ(strval,"",MEMINFO);
	for( i = 0; i < LEN_LIST_OBJ(items); ++i ){
		s = GET_ENTRY_LIST_OBJ(items,i);
		newkey = VAL_STR_OBJ(strval);
		APPEND_STR_OBJ(MEMINFO,strval,ISNULL(newkey)?"":" ",s,0);
	}
	newkey = VAL_STR_OBJ(strval);
	if( ISNULL(newkey) ){
		Errorcode = JABORT;
		FATAL(LOG_INFO) "Make_new_model_entry: bad key entry '%s'", key );
	}
	DEBUGF(DDB3)("Make_new_model_entry: final key '%s'", newkey );
	/* now we add this entry to all of the single key values */ 
	for( i = 0; i < LEN_LIST_OBJ(items); ++i ){
		s = GET_ENTRY_LIST_OBJ(items,i);
		p = GET_HASH_OBJ(index,s);
		if( !p ){
			p = SET_HASH_OBJ(index,s,0,MEMINFO);
		}
		APPEND_STR_OBJ(MEMINFO,p,",",newkey,0);
		DEBUGF(DDB3)("Make_new_model_entry: key list for '%s' = '%s'", s, VAL_STR_OBJ(p) );
	}
	DEBUGF(DDB3)("Make_new_model_entry: getting model for key '%s'", newkey );
	if( !(current_entry = GET_HASH_OBJ( entries, newkey )) ){
		current_entry = SET_HASH_OBJ( entries, newkey, 0, MEMINFO );
	}
	/* now we add values to the entry */
	DEBUGFC(DDB2){
		char msg[SMALLBUFFER];
		SNPRINTF(msg,sizeof(msg)) "Make_new_model_entry: current values for key '%s'", newkey );
		SHORT_DUMP_OBJ(msg, current_entry );
	}
	return( current_entry );
}

/*
 * void Select_model_info(
 * OBJ *model     - this is where you put the output
 * OBJ *index     - the index table for the entries
 * OBJ *entries   - the actual entries themselves
 * char *id       - the id of the thing we are looking up
 * int level, int maxlevel - recursion control and checking
 *
 *  Index:  'default' =>"default"
 *  Index:  'xx' =>'xx yy zz,xx'
 *  Index:  'yy' =>'xx yy zz'
 *  Index:  'zz' =>'xx yy zz'
 *    The index has a list of strings which are the entries
 *    into the 'entries' table.
 *
 *  Entries:  'default'  => dkey = dvalue... dkey += value... dkey -= value..
 *  Entries:  'xx yy zz' =>  key =  value...  key += value...  key -= value..
 *  Entries:  'xx'       =>  key =  value...
 */
void Select_model_info( OBJ *model, OBJ *index, OBJ *entries, char *id, int level, int maxlevel )
{
	char *names;
	int i, j;
	OBJ *list = 0, *hash = 0, *tc_list = 0;
	DEBUGF(DDB1)("Select_model_info: id '%s', depth %d, max %d", id, level, maxlevel );
	if( level > maxlevel ){
		Errorcode = JABORT;
		LOGMSG(LOG_INFO)_("Select_model_info: looking for '%s' and recursion too deep at model '%s'"), Model_id, id );
	}
	names = GET_HASH_STR_OBJ(index,id,MEMINFO);
	DEBUGF(DDB1)("Select_model_info: id '%s', names '%s'", id, names );
	if( !names ){
		Errorcode = JABORT;
		FATAL(LOG_INFO)_("Select_model_info: looking for '%s' %s cannot find information for model '%s'"),
			Model_id,
			level?"recursive lookup and":"", id );
	}
	list = Split_STR_OBJ( /*p*/list, /*nomod*/1, /*str*/names,
		/*type*/OBJ_T_LIST, /*linesep*/",", /*escape*/0,
		/*comment*/ 0, /*trim*/1, /*keysep*/"=",/*do_append*/',', /*lc*/1,
		/*flagvalues*/1, /*value_urlunesc*/0, MEMINFO );
	for( i = 0; i < LEN_LIST_OBJ(list); ++i ){
		char *tc;
		char *name = GET_ENTRY_LIST_OBJ(list,i);
		char *value = GET_HASH_STR_OBJ( entries, name, MEMINFO);
		DEBUGF(DDB2)("Select_model_info: processing name '%s', value '%s'", name, value );
		/* we add the entries to the Model information */
		hash = NEW_OBJ(hash,MEMINFO);
		Split_STR_OBJ( /*p*/hash, /*nomod*/1, /*str*/value,
			/*type*/OBJ_T_HASH, /*linesep*/"\001", /*escape*/0,
			/*comment*/ 0, /*trim*/1, /*keysep*/"=",/*do_append*/' ', /*lc*/1,
			/*flagvalues*/ 1, /*value_urlunesc*/0, MEMINFO );
		DEBUGFC(DDB2){ SHORT_DUMP_OBJ("Select_model_info: split entry", hash ); }
		if( (tc = GET_HASH_STR_OBJ(hash,"tc",MEMINFO)) ){
			char *s;
			DEBUGF(DDB2)("Select_model_info: tc entry '%s'", tc );
			/* RECURSION!!! - get rid of [ and ] */
			s = tc;
			while( s && (s = safestrpbrk(s,"[]")) ){
				*s++ = ' ';
			}
			tc_list = NEW_OBJ(tc_list,MEMINFO);
			tc_list = Split_STR_OBJ( /*p*/tc_list, /*nomod*/1, /*str*/tc,
			/*type*/OBJ_T_LIST, /*linesep*/LISTSEP WHITESPACE, /*escape*/0,
			/*comment*/ 0, /*trim*/1, /*keysep*/"=",/*do_append*/',', /*lc*/1,
			/*flagvalues*/ 1, /*value_urlunesc*/0, MEMINFO );
			/* OK, we remove the 'tc' information */
			SET_HASH_STR_OBJ( model, "tc", 0, MEMINFO );
			for( j = 0; j < LEN_LIST_OBJ(tc_list); ++j ){
				char *tc_name = GET_ENTRY_LIST_OBJ(tc_list,j);
				Select_model_info( model, index, entries, tc_name, level+1, maxlevel );
			}
			SET_HASH_STR_OBJ( hash, "tc", 0, MEMINFO );
		}
		/* now we add the information in this entry */
		Split_STR_OBJ( /*p*/model, /*nomod*/1, /*str*/value,
			/*type*/OBJ_T_HASH, /*linesep*/"\001", /*escape*/0,
			/*comment*/ 0, /*trim*/1, /*keysep*/"=",/*do_append*/' ', /*lc*/1,
			/*flagvalues*/ 1, /*value_urlunesc*/0, MEMINFO );
		SET_HASH_STR_OBJ( model, "tc", 0, MEMINFO );
	}
	DEBUGFC(DDB1){
		char msg[SMALLBUFFER];
		SNPRINTF(msg,sizeof(msg)) "Select_model_info: for '%s'", id );
		SHORT_DUMP_OBJ(msg, model );
	}
	if( hash ) FREE_OBJ( hash ); hash = 0;
}

/*
 * Get_prog_status -
 *   run a program and update the devstatus object
 *   with values returned
 *   if exit is 0, set sync = 1, waitend = 1
 */

int Get_prog_status( OBJ *devstatus, char *pgm, int timeout )
{
	OBJ *l = 0;
	int error_fd[2], pid, len, n, c;
	char buffer[SMALLBUFFER];
	plp_status_t status;
	char *s;

	l = NEW_OBJ(l,MEMINFO);

	pgm = safestrdup(pgm,MEMINFO);
	while( (c = cval(pgm)) == '|' || isspace(c)) ++pgm;

	DEBUG4("Get_prog_status: pgm '%s'", pgm );
	if( pgm && (safestrpbrk( pgm, "|><;" ) || (cval(pgm) == '(')) ){
		APPEND_LIST_OBJ(l,"/bin/sh", MEMINFO);
		APPEND_LIST_OBJ(l,"-c", MEMINFO);
		if( cval(pgm) != '(' ){
			char *s = safestrdup3("( ",pgm," )", MEMINFO );
			APPEND_LIST_OBJ(l, s, MEMINFO );
			if(s) SAFEFREE(s); s = 0;
		} else {
			APPEND_LIST_OBJ(l, pgm, MEMINFO );
		}
	} else {
		Split_cmd_line_OBJ( l, pgm );
	}
	if(DEBUGL2)SHORT_DUMP_OBJ("Get_prog_status: process args", l );
	if( pipe(error_fd) == -1 ){
		Errorcode = JABORT;
		LOGERR_DIE(LOGINFO)_("Get_prog_status: pipe failed"));
	}
	Set_max_fd(error_fd[0]); Set_max_fd(error_fd[1]);

	/* fork the process */
	if( (pid = fork()) == -1 ){
		Errorcode = JABORT;
		LOGERR_DIE(LOGINFO)_("Get_prog_status: fork failed"));
	} else if( pid == 0 ){
		if( dup2(1,0) == -1
			|| dup2(error_fd[1],1) == -1
			|| dup2(error_fd[1],2) == -1 ){
			Errorcode = JABORT;
			LOGERR_DIE(LOGINFO)_("Get_prog_status: dup2 failed"));
		}
		close_on_exec(3);
		/* now we exec the filter process */
		{
			char **argv = GET_LIST_OBJ(l);
			execve( argv[0], argv, Envp );
			/* ooops... error */
			SNPRINTF(buffer,sizeof(buffer))  _("execv '%s' failed - %s"), argv[0],
				Errormsg(errno) );
			Write_fd_str(2,buffer);
		}
		exit(JABORT);
	}

	close(error_fd[1]);
	SNPRINTF(buffer, sizeof(buffer))  "%s", pgm );
	if( (s = safestrpbrk( buffer, WHITESPACE )) ) *s = 0;
	if( (s = safestrrchr( buffer, '/' )) ){
		++s;
	} else {
		s = buffer;
	}
	LOGMSG(LOG_INFO)_("Get_prog_status: started '%s'"), s);
	len = 0;
	buffer[0] = 0;
	while( len < (int)sizeof(buffer)-1
		&& (n = read(error_fd[0],buffer,sizeof(buffer)-1)) > 0 ){
		buffer[n] = 0;
		Put_inbuf_len( buffer, n );
		Get_inbuf_str();
	}
	close(error_fd[0]);
	while( (n = waitpid(pid,&status,0)) != pid ){
		if( pid == -1 && errno == ECHILD ){
			Errorcode = JABORT;
			LOGERR_DIE(LOGINFO) _("Get_prog_status: waitpid and no child!") );
		}
		DEBUG1("Get_prog_status: converter pid %d, exit '%s'", pid,
			Decode_status( &status ) );
	}
	DEBUG1("Get_prog_status: converter pid %d, exit '%s'", pid,
		Decode_status( &status ) );
	n = 0;
	if( WIFEXITED(status) ){
		n = WEXITSTATUS(status);
		if( n ){
			if( n > 0 && n < 32 ) n += JFAIL-1;
			Errorcode = n;
			if( n != JFAIL ) Errorcode = JABORT;
			FATAL(LOGINFO)_("Get_prog_status: status process exited with %d, '%s'"),
				n, Decode_status(&status));
		}
		if( !GET_HASH_IVAL_OBJ( devstatus,"sync") ){
			SET_HASH_IVAL_OBJ( devstatus,"sync",1,MEMINFO);
		}
		if( !GET_HASH_IVAL_OBJ( devstatus,"waitend") ){
			SET_HASH_IVAL_OBJ( devstatus,"waitend",1,MEMINFO);
		}
	} else if( WIFSIGNALED(status) ){
		n = WTERMSIG(status);
		Errorcode = JABORT;
		FATAL(LOGINFO)_("Get_prog_status: converter process died with signal %d, '%s'"),
			n, Sigstr(n));
	}
	DEBUG1("Get_prog_status: freeing pgm");
	if( pgm ) SAFEFREE(pgm); pgm = 0;
	DEBUG1("Get_prog_status: done freeing pgm");
	return( n );
}
