<commit_before>/***************************************************************
 *
 * Copyright (C) 1990-2007, Condor Team, Computer Sciences Department,
 * University of Wisconsin-Madison, WI.
 * 
 * Licensed under the Apache License, Version 2.0 (the "License"); you
 * may not use this file except in compliance with the License.  You may
 * obtain a copy of the License at
 * 
 *    http://www.apache.org/licenses/LICENSE-2.0
 * 
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 *
 ***************************************************************/


#include "condor_common.h"
#include "condor_debug.h"
#include "condor_config.h"
#include "condor_uid.h"
#include "master.h"
#include "condor_daemon_core.h"
#include "exit.h"
#include "my_hostname.h"
#include "basename.h"
#include "condor_email.h"
#include "condor_environ.h"
#include "condor_parameters.h"
#include "daemon_list.h"
#include "sig_name.h"
#include "internet.h"
#include "strupr.h"
#include "condor_netdb.h"
#include "file_sql.h"
#include "file_lock.h"

#if HAVE_DLOPEN
#include "MasterPlugin.h"
#endif

// these are defined in master.C
extern int 		MasterLockFD;
extern FileLock*	MasterLock;
extern int		master_exit(int);
extern int		update_interval;
extern int		check_new_exec_interval;
extern int		preen_interval;
extern int		new_bin_delay;
extern char*	FS_Preen;
extern			ClassAd* ad;
extern int		NT_ServiceFlag; // TRUE if running on NT as an NT Service

extern time_t	GetTimeStamp(char* file);
extern int 	   	NewExecutable(char* file, time_t* tsp);
extern void		tail_log( FILE*, char*, int );
extern int		run_preen(Service*);

extern FILESQL *FILEObj;
extern int condor_main_argc;
extern char **condor_main_argv;
extern time_t daemon_stop_time;

int		hourly_housekeeping(void);

// to add a new process as a condor daemon, just add one line in 
// the structure below. The first elemnt is the string that is 
// looked for in the config file for the executable, and the
// second element is the parameter looked for in the confit
// file for the name of the corresponding log file.If no log
// file need be there, then put a zero in the second column.
// The third parameter is the name of a condor_config variable
// that is ckecked before the process is created. If it is zero, 
// then the process is created always. If it is a valid name,
// this name shud be set to true in the condor_config file for the
// process to be created.

// make sure that the master does not start itself : set runs_here off

extern Daemons 		daemons;
extern int			master_backoff_constant;	// Backoff time constant
extern int			master_backoff_ceiling;		// Backoff time ceiling
extern float		master_backoff_factor;		// exponential factor
extern int			master_recover_time;		// recovering time
extern int			shutdown_graceful_timeout;
extern int			shutdown_fast_timeout;
extern int			Lines;
extern int			PublishObituaries;
extern int			StartDaemons;
extern int			GotDaemonsOff;
extern char*		MasterName;

///////////////////////////////////////////////////////////////////////////
// daemon Class
///////////////////////////////////////////////////////////////////////////
daemon::daemon(char *name, bool is_daemon_core, bool is_h )
{
	char	buf[1000];

	name_in_config_file = strdup(name);
	daemon_name = strchr(name_in_config_file, '-');
	if(daemon_name)
	{
		daemon_name++;
		if(*daemon_name == '_')
		{
			daemon_name++;
			if(*daemon_name == '\0')
			{
				daemon_name = NULL;
			}
		}
		else
		{
			daemon_name = NULL;
		}
	} 

	// Store our HA state
	this->ha_lock = NULL;
	this->is_ha = is_h;

	// Default to not on hold (will be set to true if controlled (i.e by HAD))
	on_hold = FALSE;

	// Handle configuration
	DoConfig( true );

	// Default log file name...
	sprintf(buf, "%s_LOG", name);
	log_filename_in_config_file = strdup(buf);

	// Check the process name (is it me?)
	process_name = NULL;
    watch_name = NULL;
	log_name = NULL;
	if( strcmp(name, "MASTER") == MATCH ) {
		runs_here = FALSE;
	} else {
		runs_here = TRUE;
	}
	runs_on_this_host();
	pid = 0;
	restarts = 0;
	newExec = FALSE; 
	timeStamp = 0;
	startTime = 0;
	isDC = is_daemon_core;
	start_tid = -1;
	recover_tid = -1;
	stop_tid = -1;
	stop_fast_tid = -1;
 	hard_kill_tid = -1;
	stop_state = NONE;
	needs_update = FALSE;
	num_controllees = 0;

#if 0
	port = NULL;
	config_info_file = NULL;
#endif
	type = stringToDaemonType( name );
	daemons.RegisterDaemon(this);
}

int
daemon::runs_on_this_host()
{
	char	*tmp;
	char	hostname[512];
	static char	**this_host_addr_list = 0;
	static int		host_addr_count;
	struct hostent	*hp;
	int		i, j;


	if ( flag_in_config_file != NULL ) {
		if (strncmp(flag_in_config_file, "BOOL_", 5) == MATCH) {
			tmp	= param( flag_in_config_file);
			if ( tmp && (*tmp == 't' || *tmp == 'T')) {
				runs_here = TRUE;
			} else {
				runs_here = FALSE;
			}
		} else {
			if (this_host_addr_list == 0) {
				if (condor_gethostname(hostname, sizeof(hostname)) < 0) {
					EXCEPT( "gethostname(0x%p,%d)", hostname, 
						   sizeof(hostname));
				}
				if( (hp=condor_gethostbyname(hostname)) == 0 ) {
					EXCEPT( "gethostbyname(%s)", hostname );
				}
				for (host_addr_count = 0; hp->h_addr_list[host_addr_count];
					 host_addr_count++ ) {
					/* Empty Loop */
				}
				this_host_addr_list = (char **) malloc(host_addr_count * 
													   sizeof (char *));
				for (i = 0; i < host_addr_count; i++) {
					this_host_addr_list[i] = (char *) malloc(hp->h_length);
					memcpy(this_host_addr_list[i], hp->h_addr_list[i],
						   hp->h_length);
				}
			}
			/* Get the name of the host on which this daemon should run */
			tmp = param( flag_in_config_file );
			if (!tmp) {
				dprintf(D_ALWAYS, "config file parameter %s not specified",
						flag_in_config_file);
				return FALSE;
			}
			runs_here = FALSE;
			hp = condor_gethostbyname( tmp );
			if (hp == 0) {
				dprintf(D_ALWAYS, "Master couldn't lookup host %s\n", tmp);
				return FALSE;
			} 
			for (i = 0; i < host_addr_count; i++) {
				for (j = 0; hp->h_addr_list[j]; j++) {
					if (memcmp(this_host_addr_list[i], hp->h_addr_list[j],
							   hp->h_length) == MATCH) {
						runs_here = TRUE;
						break;
					}
				}
			}
		}
	}
	if(strcmp(name_in_config_file, "KBDD") == 0)
	// X_RUNS_HERE controls whether or not to run kbdd if it's presented in
	// the config file
	{
		tmp = param("X_RUNS_HERE");
		if(tmp)
		{
			if(*tmp == 'T' || *tmp == 't')
			{
				runs_here = TRUE;
			}
			else
			{
				runs_here = FALSE;
			}
		free(tmp);
		}
	}
	return runs_here;
}


void
daemon::Recover()
{
	restarts = 0;
	recover_tid = -1; 
	dprintf(D_FULLDEBUG, "%s recovered\n", name_in_config_file);
}


int
daemon::NextStart()
{
	int seconds;
	seconds = m_backoff_constant + (int)ceil(::pow(m_backoff_factor, restarts));
	if( seconds > m_backoff_ceiling || seconds < 0 ) {
		seconds = m_backoff_ceiling;
	}
	return seconds;
}


int daemon::Restart()
{
	int		n;

	if ( controller && ( restarts >= 2 ) ) {
		dprintf( D_ALWAYS, "Telling %s's controller (%s)\n",
				 name_in_config_file, controller->name_in_config_file );
		controller->Stop( );
		on_hold = true;
	}
	if( on_hold ) {
		return FALSE;
	}
	if(newExec == TRUE) {
		restarts = 0;
		newExec = FALSE; 
		n = new_bin_delay;
	} else {
		n = NextStart();
		restarts++;
	}

	if( recover_tid != -1 ) {
		dprintf( D_FULLDEBUG, 
				 "Cancelling recovering timer (%d) for %s\n", 
				 recover_tid, process_name );
		daemonCore->Cancel_Timer( recover_tid );
		recover_tid = -1;
	}
	if( start_tid != -1 ) {
		daemonCore->Cancel_Timer( start_tid );
	}
	start_tid = daemonCore->
		Register_Timer( n, (TimerHandlercpp)&daemon::DoStart,
						"daemon::DoStart()", this );
	dprintf(D_ALWAYS, "restarting %s in %d seconds\n", process_name, n);

		// Update the CM now so we see that this daemon is down.
	daemons.UpdateCollector();

		// Set a flag so Start() knows to update once it's up again.
	needs_update = TRUE;

	return 1;
}


// This little function is used so when the start timer goes off, we
// can set the start_tid back to -1 before we actually call Start().
void
daemon::DoStart()
{
	start_tid = -1;
	Start( true );		// Don't forward this on to the controller!
}


// This function handles all of the configuration stuff; at startup and
// at reconfig time.
void
daemon::DoConfig( bool init )
{
	char	*tmp;
	char	buf[1000];

	// Initialize some variables the first time through
	if ( init ) {
		flag_in_config_file = NULL;
	}

	// Check for the _FLAG parameter
	sprintf(buf, "%s_FLAG", name_in_config_file );
	tmp = param(buf);

	// Previously defind?
	if ( NULL != flag_in_config_file ) {
		// Has it changed?
		if ( strcmp( flag_in_config_file, tmp ) ) {
			free( flag_in_config_file );
			flag_in_config_file = tmp;
		} else if ( NULL != tmp ) {
			// Unchanged, just free up tmp
			free( tmp );
		} else {
			// Do nothing
		}
	} else {
		// Not previously defined; just use tmp whatever it is
		flag_in_config_file = tmp;
	}

	// High Availability?
	if ( is_ha ) {
		if ( SetupHighAvailability( ) < 0 ) {
			// What should we do here????
		}
	}

	// get env settings from config file if present
	sprintf(buf, "%s_ENVIRONMENT", name_in_config_file );
	char *env_string = param( buf );

	Env env_parser;
	MyString env_error_msg;

	if(!env_parser.MergeFromV1RawOrV2Quoted(env_string,&env_error_msg)) {
		EXCEPT("ERROR: Failed to parse %s_ENVIRONMENT in config file: %s\n",
		       name_in_config_file,
			   env_error_msg.Value());
	}
	free(env_string);

	this->env.Clear();
	this->env.MergeFrom(env_parser);

	// Check for the _INITIAL_STATE parameter (only at init time)
	// Default to on_hold = false, set to true of state eq "off"
	if ( init ) {
		sprintf(buf, "MASTER_%s_CONTROLLER", name_in_config_file );
		controller_name = param( buf );
		controller = NULL;		// Setup later in Daemons::CheckDaemonConfig()
		on_hold = true;
		if ( controller_name ) {
			dprintf( D_FULLDEBUG, "Daemon %s is controlled by %s\n",
					 name_in_config_file, controller_name );
		}
	}

	// XXX These defaults look to be very wrong, compare with 
	// MASTER_BACKOFF_*.
	sprintf(buf, "MASTER_%s_BACKOFF_CONSTANT", name_in_config_file );
	m_backoff_constant = param_integer( buf, master_backoff_constant, 1 );

	sprintf(buf, "MASTER_%s_BACKOFF_CEILING", name_in_config_file );
	m_backoff_ceiling = param_integer( buf, master_backoff_ceiling, 1 );

	sprintf(buf, "MASTER_%s_BACKOFF_FACTOR", name_in_config_file );
	m_backoff_factor = param_double( buf, master_backoff_factor, 0 );
	if( m_backoff_factor <= 0.0 ) {
    	m_backoff_factor = master_backoff_factor;
    }
	
	sprintf(buf, "MASTER_%s_RECOVER_FACTOR", name_in_config_file );
	m_recover_time = param_integer( buf, master_recover_time, 1 );


	// Weiru
	// In the case that we have several for example schedds running on the
	// same machine, we specify SCHEDD__1, SCHEDD_2, ... in the config file.
	// We want to be able to specify only one executable for them as well as
	// different executables, or one flag for all of them as well as
	// different flags for each. So we instead of specifying in config file
	// SCHEDD__1 = /unsup/condor/bin/condor_schedd, SCHEDD__2 = /unsup/condor/
	// bin/condor_schedd, ... we can simply say SCHEDD = /unsup/...
	if( ( NULL == flag_in_config_file ) && ( NULL != daemon_name ) ) {
		*(daemon_name - 2) = '\0';
		sprintf(buf, "%s_FLAG", name_in_config_file);
		flag_in_config_file = param(buf);
		*(daemon_name - 2) = '_';
	} 

}

void
daemon::Hold( bool hold, bool never_forward )
{
	if ( controller && !never_forward ) {
		dprintf( D_FULLDEBUG, "Forwarding Hold to %s's controller (%s)\n",
				 name_in_config_file, controller->name_in_config_file );
		controller->Hold( hold );
	} else {
		this->on_hold = hold;
	}
}

int
daemon::Start( bool never_forward )
{
	if ( controller ) {
		if ( !never_forward ) {
			dprintf( D_FULLDEBUG,
					 "Forwarding Start to %s's controller (%s)\n",
					 name_in_config_file, controller->name_in_config_file );
			return controller->Start( );
		} else {
			dprintf( D_FULLDEBUG,
					 "Handling Start for %s myself\n",
					 name_in_config_file );
		}
	}
	if( start_tid != -1 ) {
		daemonCore->Cancel_Timer( start_tid );
		start_tid = -1;
	}
	if( on_hold ) {
		return FALSE;
	}
	if( pid ) {
			// Already running
		return TRUE;
	}

		// If this is an HA service, we need to lock it first.
	if ( is_ha && ha_lock ) {
		int lockstat = ha_lock->AcquireLock( true );
		if ( lockstat < 0 ) {
			dprintf(D_ALWAYS, "%s: HA lock error\n", name_in_config_file );
		} else if ( lockstat > 0 ) {
			dprintf(D_FULLDEBUG, "%s: HA lock busy\n", name_in_config_file );
		}

			// Here, we have the lock // The LockAcquired callback
			// will have already fired up the daemon for us.
		return 0;
	}

	// Now, go start the process
	return RealStart( );
}

// The real start logic; this is called by the LockAcquired callback when
// we've acquired the lock
int daemon::RealStart( )
{
	const char	*shortname;
	int 	command_port = isDC ? TRUE : FALSE;
	char	buf[512];
	ArgList args;

	// Copy a couple of checks from Start
	dprintf( D_FULLDEBUG, "::RealStart; %s on_hold=%d\n", name_in_config_file, on_hold );
	if( on_hold ) {
		return FALSE;
	}
	if( pid ) {
			// Already running
		return TRUE;
	}
		// Check for a controller. If one exists, e.g. HAD, it is
		// likely this daemon has stop_state = FAST, but we want to
		// let it start anyway. The stop_state != NONE check is to
		// avoid letting the master start HA daemons it is
		// maintaining. A controller indicates something else is
		// maintaining the daemon.
	if( !controller && stop_state != NONE ) {
			// Currently trying to shutdown, this might happen for an
			// HA daemon who acquires a lock just before shutdown.
		dprintf( D_FULLDEBUG, "::RealStart; %s stop_state=%d, ignoring\n",
				 name_in_config_file, stop_state );
		return FALSE;
	}

	shortname = condor_basename( process_name );

	if( access(process_name,X_OK) != 0 ) {
		dprintf(D_ALWAYS, "%s: Cannot execute\n", process_name );
		pid = 0; 
		// Schedule to try and restart it a little later
		Restart();
		return 0;
	}

		// Collector needs to listen on a well known port, as does the
		// Negotiator.  
		// We didn't want them to use root for any reason, but b/c of
		// evil in the security code where we're looking up host certs
		// in the keytab file, we still need root afterall. :(
	bool wants_condor_priv = false;
	if ( strcmp(name_in_config_file,"COLLECTOR") == 0 ) {
			// **** OLD
			// If we're spawning a collector, we can get the right
			// port by asking the global Collector object for it,
			// since we've already instantiated that with the info for
			// the local pool's collector.  This also saves the
			// trouble of instantiating a new DCCollector object,
			// which duplicates some effort and is less efficient. 
			// **** END OLD

			// ckireyev 09/10/04
			// Now that we have multiple collectors, the way to figure out
			// the port on this machine, is to go through all of the
			// collectors until we find the one for THIS machine. Then
			// get the port from that entry
		command_port = -1;

		dprintf ( 
			D_FULLDEBUG, 
			"Looking for matching Collector on '%s' ...\n", 
			my_full_hostname () );
		CollectorList* collectors = NULL;
		if ((collectors = daemonCore->getCollectorList())) {
			char * my_hostname = my_full_hostname();
			Daemon * my_daemon;
			collectors->rewind();
			while (collectors->next (my_daemon)) {

				dprintf ( 
					D_FULLDEBUG, 
					"Matching '%s:%d'\n", 
					my_daemon->fullHostname (),
					my_daemon->port () );

				if (same_host (my_hostname, 
							   my_daemon->fullHostname())) {
					command_port = my_daemon->port();
					dprintf ( D_FULLDEBUG, "Host name matches.\n" );
					break;
				}
			}
				// If the user explicitly asked for command port 0,
				// meaning "pick an ephemeral port", we need to pass
				// 1 to CreateProcess, as the command_port is overloaded
				// to mean "is this a DC process", and if we pass 0 in
				// DC will start the collector as a non DC process.
				// DC special cases command_port = 1 to mean "any"
			if (command_port == 0) {
				command_port = 1;
			}
		}
		dprintf ( 
			D_FULLDEBUG, 
			"Finished looking for Collectors.\n" );

		if (command_port == -1) {
				// strange....
			command_port = COLLECTOR_PORT;
			dprintf (D_ALWAYS, "Collector port not defined, will use default: %d\n", COLLECTOR_PORT);
		}

		dprintf (D_FULLDEBUG, "Starting Collector on port %d\n", command_port);


			// We can't do this b/c of needing to read host certs as root 
			// wants_condor_priv = true;
	} else if( stricmp(name_in_config_file,"CONDOR_VIEW") == 0 ||
			   stricmp(name_in_config_file,"VIEW_SERVER") == 0 ) {
		Daemon d( DT_VIEW_COLLECTOR );
		command_port = d.port();
			// We can't do this b/c of needing to read host certs as root 
			// wants_condor_priv = true;
	} 
	else if( strcmp(name_in_config_file,"NEGOTIATOR") == 0 ) {
		char* host = getCmHostFromConfig( "NEGOTIATOR" );
		if( host ) {
			free (host);
			Daemon d( DT_NEGOTIATOR );
			command_port = d.port();
			// We can't do this b/c of needing to read host certs as root 
			// wants_condor_priv = true;
		}
	}

	priv_state priv_mode = PRIV_ROOT;
	
	snprintf(buf, sizeof(buf), "%s_USERID",name_in_config_file);
	char * username = param( buf );
	if(username) {
		// domain is set to NULL since we don't care about the domain
		// unless we're on Windows, and on Windows the master
		// always runs as SYSTEM (as a service). --stolley
		int result = init_user_ids(username, NULL);
		if(result) {
			priv_mode = PRIV_USER_FINAL;
		} else {
			dprintf(D_ALWAYS,"couldn't switch to user %s!\n",username);
		}
		free(username);
	}
	if( wants_condor_priv && priv_mode == PRIV_ROOT ) {

#ifndef	WIN32

			// we go this route on UNIX b/c it's safer, easier, and it
			// automatically gets it right if CONDOR_IDS is defined.
		uid_t cuid = get_condor_uid();
		gid_t cgid = get_condor_gid();
		int result = set_user_ids( cuid, cgid );
		if( result ) { 
			priv_mode = PRIV_USER_FINAL;
		} else {
			dprintf( D_ALWAYS, 
					 "couldn't switch to \"condor\" user %d.%d!\n",
					 cuid, cgid );
		}

#else /* WIN32 */

			// don't know what to do here just yet...

#endif /* WIN32 */
	}

	args.AppendArg(shortname);
	if(isDC) {
		args.AppendArg("-f");
	}

	snprintf( buf, sizeof( buf ), "%s_ARGS", name_in_config_file );
	char *daemon_args = param( buf );

	MyString args_error;
	if(!args.AppendArgsV1RawOrV2Quoted(daemon_args,&args_error)) {
		dprintf(D_ALWAYS,"ERROR: failed to parse %s daemon arguments: %s\n",
				buf,
				args_error.Value());
		Restart();
		free(daemon_args);
		return 0;
	}

	free(daemon_args);

    // The below chunk is for HAD support

    // take command port from arguments( buf )
    // Don't mess with buf or tmp (they are not our variables) -
    // allocate them again
    if ( isDC ) {
		int i;
		for(i=0;i<args.Count();i++) {
			char const *cur_arg = args.GetArg(i);
			if(strcmp( cur_arg, "-p" ) == 0 ) {
				if(i+1<args.Count()) {
					char const *port_arg = args.GetArg(i+1);
					command_port = atoi(port_arg);
				}
			}
		}
    }

	// set up a FamilyInfo structure so Daemon Core registers a process family
	// for this daemon with the procd
	//
	FamilyInfo fi;
	fi.max_snapshot_interval = param_integer("PID_SNAPSHOT_INTERVAL", 60);

	pid = daemonCore->Create_Process(
				process_name,	// program to exec
				args,			// args
				priv_mode,		// privledge level
				1,				// which reaper ID to use; use default reaper
				command_port,	// port to use for command port; TRUE=choose one dynamically
				&env,			// environment
				NULL,			// current working directory
				&fi);			// we want a new process family

	if ( pid == FALSE ) {
		// Create_Process failed!
		dprintf( D_FAILURE|D_ALWAYS,
				 "ERROR: Create_Process failed trying to start %s\n",
				 process_name);
		pid = 0;
		// Schedule to try and start it a litle later
		Restart();
		return 0;
	}

	// if this is a restart, start recover timer
	if (restarts > 0) {
		recover_tid = daemonCore->Register_Timer( m_recover_time,
						(TimerHandlercpp)&daemon::Recover,
						"daemon::Recover()", this );
		dprintf(D_FULLDEBUG, "start recover timer (%d)\n", recover_tid);
	}

	if (command_port) {
		dprintf( D_FAILURE|D_ALWAYS,
				 "Started DaemonCore process \"%s\", pid and pgroup = %d\n",
				 process_name, pid );
	} else {
		dprintf( D_ALWAYS,
				 "Started process \"%s\", pid and pgroup = %d\n",
				 process_name, pid );
	}
	
		// Make sure we've got the current timestamp for updates, etc.
	timeStamp = GetTimeStamp(watch_name);

		// record the time we were started
	startTime = time(0);

		// Since we just started it, we know it's not a new executable. 
	newExec = FALSE;

		// Be sure to reset the stop_state, if the daemon is HAD
		// managed it will have been set to FAST on startup
	stop_state = NONE;

		// If starting the collector, give it a few seconds to get
		// going before starting other daemons or talking to ti
		// ourselves<commit_msg>Master now prints full command line if D_FULLDEBUG is enabled<commit_after>/***************************************************************
 *
 * Copyright (C) 1990-2007, Condor Team, Computer Sciences Department,
 * University of Wisconsin-Madison, WI.
 * 
 * Licensed under the Apache License, Version 2.0 (the "License"); you
 * may not use this file except in compliance with the License.  You may
 * obtain a copy of the License at
 * 
 *    http://www.apache.org/licenses/LICENSE-2.0
 * 
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 *
 ***************************************************************/


#include "condor_common.h"
#include "condor_debug.h"
#include "condor_config.h"
#include "condor_uid.h"
#include "master.h"
#include "condor_daemon_core.h"
#include "exit.h"
#include "my_hostname.h"
#include "basename.h"
#include "condor_email.h"
#include "condor_environ.h"
#include "condor_parameters.h"
#include "daemon_list.h"
#include "sig_name.h"
#include "internet.h"
#include "strupr.h"
#include "condor_netdb.h"
#include "file_sql.h"
#include "file_lock.h"

#if HAVE_DLOPEN
#include "MasterPlugin.h"
#endif

// these are defined in master.C
extern int 		MasterLockFD;
extern FileLock*	MasterLock;
extern int		master_exit(int);
extern int		update_interval;
extern int		check_new_exec_interval;
extern int		preen_interval;
extern int		new_bin_delay;
extern char*	FS_Preen;
extern			ClassAd* ad;
extern int		NT_ServiceFlag; // TRUE if running on NT as an NT Service

extern time_t	GetTimeStamp(char* file);
extern int 	   	NewExecutable(char* file, time_t* tsp);
extern void		tail_log( FILE*, char*, int );
extern int		run_preen(Service*);

extern FILESQL *FILEObj;
extern int condor_main_argc;
extern char **condor_main_argv;
extern time_t daemon_stop_time;

int		hourly_housekeeping(void);

// to add a new process as a condor daemon, just add one line in 
// the structure below. The first elemnt is the string that is 
// looked for in the config file for the executable, and the
// second element is the parameter looked for in the confit
// file for the name of the corresponding log file.If no log
// file need be there, then put a zero in the second column.
// The third parameter is the name of a condor_config variable
// that is ckecked before the process is created. If it is zero, 
// then the process is created always. If it is a valid name,
// this name shud be set to true in the condor_config file for the
// process to be created.

// make sure that the master does not start itself : set runs_here off

extern Daemons 		daemons;
extern int			master_backoff_constant;	// Backoff time constant
extern int			master_backoff_ceiling;		// Backoff time ceiling
extern float		master_backoff_factor;		// exponential factor
extern int			master_recover_time;		// recovering time
extern int			shutdown_graceful_timeout;
extern int			shutdown_fast_timeout;
extern int			Lines;
extern int			PublishObituaries;
extern int			StartDaemons;
extern int			GotDaemonsOff;
extern char*		MasterName;

///////////////////////////////////////////////////////////////////////////
// daemon Class
///////////////////////////////////////////////////////////////////////////
daemon::daemon(char *name, bool is_daemon_core, bool is_h )
{
	char	buf[1000];

	name_in_config_file = strdup(name);
	daemon_name = strchr(name_in_config_file, '-');
	if(daemon_name)
	{
		daemon_name++;
		if(*daemon_name == '_')
		{
			daemon_name++;
			if(*daemon_name == '\0')
			{
				daemon_name = NULL;
			}
		}
		else
		{
			daemon_name = NULL;
		}
	} 

	// Store our HA state
	this->ha_lock = NULL;
	this->is_ha = is_h;

	// Default to not on hold (will be set to true if controlled (i.e by HAD))
	on_hold = FALSE;

	// Handle configuration
	DoConfig( true );

	// Default log file name...
	sprintf(buf, "%s_LOG", name);
	log_filename_in_config_file = strdup(buf);

	// Check the process name (is it me?)
	process_name = NULL;
    watch_name = NULL;
	log_name = NULL;
	if( strcmp(name, "MASTER") == MATCH ) {
		runs_here = FALSE;
	} else {
		runs_here = TRUE;
	}
	runs_on_this_host();
	pid = 0;
	restarts = 0;
	newExec = FALSE; 
	timeStamp = 0;
	startTime = 0;
	isDC = is_daemon_core;
	start_tid = -1;
	recover_tid = -1;
	stop_tid = -1;
	stop_fast_tid = -1;
 	hard_kill_tid = -1;
	stop_state = NONE;
	needs_update = FALSE;
	num_controllees = 0;

#if 0
	port = NULL;
	config_info_file = NULL;
#endif
	type = stringToDaemonType( name );
	daemons.RegisterDaemon(this);
}

int
daemon::runs_on_this_host()
{
	char	*tmp;
	char	hostname[512];
	static char	**this_host_addr_list = 0;
	static int		host_addr_count;
	struct hostent	*hp;
	int		i, j;


	if ( flag_in_config_file != NULL ) {
		if (strncmp(flag_in_config_file, "BOOL_", 5) == MATCH) {
			tmp	= param( flag_in_config_file);
			if ( tmp && (*tmp == 't' || *tmp == 'T')) {
				runs_here = TRUE;
			} else {
				runs_here = FALSE;
			}
		} else {
			if (this_host_addr_list == 0) {
				if (condor_gethostname(hostname, sizeof(hostname)) < 0) {
					EXCEPT( "gethostname(0x%p,%d)", hostname, 
						   sizeof(hostname));
				}
				if( (hp=condor_gethostbyname(hostname)) == 0 ) {
					EXCEPT( "gethostbyname(%s)", hostname );
				}
				for (host_addr_count = 0; hp->h_addr_list[host_addr_count];
					 host_addr_count++ ) {
					/* Empty Loop */
				}
				this_host_addr_list = (char **) malloc(host_addr_count * 
													   sizeof (char *));
				for (i = 0; i < host_addr_count; i++) {
					this_host_addr_list[i] = (char *) malloc(hp->h_length);
					memcpy(this_host_addr_list[i], hp->h_addr_list[i],
						   hp->h_length);
				}
			}
			/* Get the name of the host on which this daemon should run */
			tmp = param( flag_in_config_file );
			if (!tmp) {
				dprintf(D_ALWAYS, "config file parameter %s not specified",
						flag_in_config_file);
				return FALSE;
			}
			runs_here = FALSE;
			hp = condor_gethostbyname( tmp );
			if (hp == 0) {
				dprintf(D_ALWAYS, "Master couldn't lookup host %s\n", tmp);
				return FALSE;
			} 
			for (i = 0; i < host_addr_count; i++) {
				for (j = 0; hp->h_addr_list[j]; j++) {
					if (memcmp(this_host_addr_list[i], hp->h_addr_list[j],
							   hp->h_length) == MATCH) {
						runs_here = TRUE;
						break;
					}
				}
			}
		}
	}
	if(strcmp(name_in_config_file, "KBDD") == 0)
	// X_RUNS_HERE controls whether or not to run kbdd if it's presented in
	// the config file
	{
		tmp = param("X_RUNS_HERE");
		if(tmp)
		{
			if(*tmp == 'T' || *tmp == 't')
			{
				runs_here = TRUE;
			}
			else
			{
				runs_here = FALSE;
			}
		free(tmp);
		}
	}
	return runs_here;
}


void
daemon::Recover()
{
	restarts = 0;
	recover_tid = -1; 
	dprintf(D_FULLDEBUG, "%s recovered\n", name_in_config_file);
}


int
daemon::NextStart()
{
	int seconds;
	seconds = m_backoff_constant + (int)ceil(::pow(m_backoff_factor, restarts));
	if( seconds > m_backoff_ceiling || seconds < 0 ) {
		seconds = m_backoff_ceiling;
	}
	return seconds;
}


int daemon::Restart()
{
	int		n;

	if ( controller && ( restarts >= 2 ) ) {
		dprintf( D_ALWAYS, "Telling %s's controller (%s)\n",
				 name_in_config_file, controller->name_in_config_file );
		controller->Stop( );
		on_hold = true;
	}
	if( on_hold ) {
		return FALSE;
	}
	if(newExec == TRUE) {
		restarts = 0;
		newExec = FALSE; 
		n = new_bin_delay;
	} else {
		n = NextStart();
		restarts++;
	}

	if( recover_tid != -1 ) {
		dprintf( D_FULLDEBUG, 
				 "Cancelling recovering timer (%d) for %s\n", 
				 recover_tid, process_name );
		daemonCore->Cancel_Timer( recover_tid );
		recover_tid = -1;
	}
	if( start_tid != -1 ) {
		daemonCore->Cancel_Timer( start_tid );
	}
	start_tid = daemonCore->
		Register_Timer( n, (TimerHandlercpp)&daemon::DoStart,
						"daemon::DoStart()", this );
	dprintf(D_ALWAYS, "restarting %s in %d seconds\n", process_name, n);

		// Update the CM now so we see that this daemon is down.
	daemons.UpdateCollector();

		// Set a flag so Start() knows to update once it's up again.
	needs_update = TRUE;

	return 1;
}


// This little function is used so when the start timer goes off, we
// can set the start_tid back to -1 before we actually call Start().
void
daemon::DoStart()
{
	start_tid = -1;
	Start( true );		// Don't forward this on to the controller!
}


// This function handles all of the configuration stuff; at startup and
// at reconfig time.
void
daemon::DoConfig( bool init )
{
	char	*tmp;
	char	buf[1000];

	// Initialize some variables the first time through
	if ( init ) {
		flag_in_config_file = NULL;
	}

	// Check for the _FLAG parameter
	sprintf(buf, "%s_FLAG", name_in_config_file );
	tmp = param(buf);

	// Previously defind?
	if ( NULL != flag_in_config_file ) {
		// Has it changed?
		if ( strcmp( flag_in_config_file, tmp ) ) {
			free( flag_in_config_file );
			flag_in_config_file = tmp;
		} else if ( NULL != tmp ) {
			// Unchanged, just free up tmp
			free( tmp );
		} else {
			// Do nothing
		}
	} else {
		// Not previously defined; just use tmp whatever it is
		flag_in_config_file = tmp;
	}

	// High Availability?
	if ( is_ha ) {
		if ( SetupHighAvailability( ) < 0 ) {
			// What should we do here????
		}
	}

	// get env settings from config file if present
	sprintf(buf, "%s_ENVIRONMENT", name_in_config_file );
	char *env_string = param( buf );

	Env env_parser;
	MyString env_error_msg;

	if(!env_parser.MergeFromV1RawOrV2Quoted(env_string,&env_error_msg)) {
		EXCEPT("ERROR: Failed to parse %s_ENVIRONMENT in config file: %s\n",
		       name_in_config_file,
			   env_error_msg.Value());
	}
	free(env_string);

	this->env.Clear();
	this->env.MergeFrom(env_parser);

	// Check for the _INITIAL_STATE parameter (only at init time)
	// Default to on_hold = false, set to true of state eq "off"
	if ( init ) {
		sprintf(buf, "MASTER_%s_CONTROLLER", name_in_config_file );
		controller_name = param( buf );
		controller = NULL;		// Setup later in Daemons::CheckDaemonConfig()
		on_hold = true;
		if ( controller_name ) {
			dprintf( D_FULLDEBUG, "Daemon %s is controlled by %s\n",
					 name_in_config_file, controller_name );
		}
	}

	// XXX These defaults look to be very wrong, compare with 
	// MASTER_BACKOFF_*.
	sprintf(buf, "MASTER_%s_BACKOFF_CONSTANT", name_in_config_file );
	m_backoff_constant = param_integer( buf, master_backoff_constant, 1 );

	sprintf(buf, "MASTER_%s_BACKOFF_CEILING", name_in_config_file );
	m_backoff_ceiling = param_integer( buf, master_backoff_ceiling, 1 );

	sprintf(buf, "MASTER_%s_BACKOFF_FACTOR", name_in_config_file );
	m_backoff_factor = param_double( buf, master_backoff_factor, 0 );
	if( m_backoff_factor <= 0.0 ) {
    	m_backoff_factor = master_backoff_factor;
    }
	
	sprintf(buf, "MASTER_%s_RECOVER_FACTOR", name_in_config_file );
	m_recover_time = param_integer( buf, master_recover_time, 1 );


	// Weiru
	// In the case that we have several for example schedds running on the
	// same machine, we specify SCHEDD__1, SCHEDD_2, ... in the config file.
	// We want to be able to specify only one executable for them as well as
	// different executables, or one flag for all of them as well as
	// different flags for each. So we instead of specifying in config file
	// SCHEDD__1 = /unsup/condor/bin/condor_schedd, SCHEDD__2 = /unsup/condor/
	// bin/condor_schedd, ... we can simply say SCHEDD = /unsup/...
	if( ( NULL == flag_in_config_file ) && ( NULL != daemon_name ) ) {
		*(daemon_name - 2) = '\0';
		sprintf(buf, "%s_FLAG", name_in_config_file);
		flag_in_config_file = param(buf);
		*(daemon_name - 2) = '_';
	} 

}

void
daemon::Hold( bool hold, bool never_forward )
{
	if ( controller && !never_forward ) {
		dprintf( D_FULLDEBUG, "Forwarding Hold to %s's controller (%s)\n",
				 name_in_config_file, controller->name_in_config_file );
		controller->Hold( hold );
	} else {
		this->on_hold = hold;
	}
}

int
daemon::Start( bool never_forward )
{
	if ( controller ) {
		if ( !never_forward ) {
			dprintf( D_FULLDEBUG,
					 "Forwarding Start to %s's controller (%s)\n",
					 name_in_config_file, controller->name_in_config_file );
			return controller->Start( );
		} else {
			dprintf( D_FULLDEBUG,
					 "Handling Start for %s myself\n",
					 name_in_config_file );
		}
	}
	if( start_tid != -1 ) {
		daemonCore->Cancel_Timer( start_tid );
		start_tid = -1;
	}
	if( on_hold ) {
		return FALSE;
	}
	if( pid ) {
			// Already running
		return TRUE;
	}

		// If this is an HA service, we need to lock it first.
	if ( is_ha && ha_lock ) {
		int lockstat = ha_lock->AcquireLock( true );
		if ( lockstat < 0 ) {
			dprintf(D_ALWAYS, "%s: HA lock error\n", name_in_config_file );
		} else if ( lockstat > 0 ) {
			dprintf(D_FULLDEBUG, "%s: HA lock busy\n", name_in_config_file );
		}

			// Here, we have the lock // The LockAcquired callback
			// will have already fired up the daemon for us.
		return 0;
	}

	// Now, go start the process
	return RealStart( );
}

// The real start logic; this is called by the LockAcquired callback when
// we've acquired the lock
int daemon::RealStart( )
{
	const char	*shortname;
	int 	command_port = isDC ? TRUE : FALSE;
	char	buf[512];
	ArgList args;

	// Copy a couple of checks from Start
	dprintf( D_FULLDEBUG, "::RealStart; %s on_hold=%d\n", name_in_config_file, on_hold );
	if( on_hold ) {
		return FALSE;
	}
	if( pid ) {
			// Already running
		return TRUE;
	}
		// Check for a controller. If one exists, e.g. HAD, it is
		// likely this daemon has stop_state = FAST, but we want to
		// let it start anyway. The stop_state != NONE check is to
		// avoid letting the master start HA daemons it is
		// maintaining. A controller indicates something else is
		// maintaining the daemon.
	if( !controller && stop_state != NONE ) {
			// Currently trying to shutdown, this might happen for an
			// HA daemon who acquires a lock just before shutdown.
		dprintf( D_FULLDEBUG, "::RealStart; %s stop_state=%d, ignoring\n",
				 name_in_config_file, stop_state );
		return FALSE;
	}

	shortname = condor_basename( process_name );

	if( access(process_name,X_OK) != 0 ) {
		dprintf(D_ALWAYS, "%s: Cannot execute\n", process_name );
		pid = 0; 
		// Schedule to try and restart it a little later
		Restart();
		return 0;
	}

		// Collector needs to listen on a well known port, as does the
		// Negotiator.  
		// We didn't want them to use root for any reason, but b/c of
		// evil in the security code where we're looking up host certs
		// in the keytab file, we still need root afterall. :(
	bool wants_condor_priv = false;
	if ( strcmp(name_in_config_file,"COLLECTOR") == 0 ) {
			// **** OLD
			// If we're spawning a collector, we can get the right
			// port by asking the global Collector object for it,
			// since we've already instantiated that with the info for
			// the local pool's collector.  This also saves the
			// trouble of instantiating a new DCCollector object,
			// which duplicates some effort and is less efficient. 
			// **** END OLD

			// ckireyev 09/10/04
			// Now that we have multiple collectors, the way to figure out
			// the port on this machine, is to go through all of the
			// collectors until we find the one for THIS machine. Then
			// get the port from that entry
		command_port = -1;

		dprintf ( 
			D_FULLDEBUG, 
			"Looking for matching Collector on '%s' ...\n", 
			my_full_hostname () );
		CollectorList* collectors = NULL;
		if ((collectors = daemonCore->getCollectorList())) {
			char * my_hostname = my_full_hostname();
			Daemon * my_daemon;
			collectors->rewind();
			while (collectors->next (my_daemon)) {

				dprintf ( 
					D_FULLDEBUG, 
					"Matching '%s:%d'\n", 
					my_daemon->fullHostname (),
					my_daemon->port () );

				if (same_host (my_hostname, 
							   my_daemon->fullHostname())) {
					command_port = my_daemon->port();
					dprintf ( D_FULLDEBUG, "Host name matches.\n" );
					break;
				}
			}
				// If the user explicitly asked for command port 0,
				// meaning "pick an ephemeral port", we need to pass
				// 1 to CreateProcess, as the command_port is overloaded
				// to mean "is this a DC process", and if we pass 0 in
				// DC will start the collector as a non DC process.
				// DC special cases command_port = 1 to mean "any"
			if (command_port == 0) {
				command_port = 1;
			}
		}
		dprintf ( 
			D_FULLDEBUG, 
			"Finished looking for Collectors.\n" );

		if (command_port == -1) {
				// strange....
			command_port = COLLECTOR_PORT;
			dprintf (D_ALWAYS, "Collector port not defined, will use default: %d\n", COLLECTOR_PORT);
		}

		dprintf (D_FULLDEBUG, "Starting Collector on port %d\n", command_port);


			// We can't do this b/c of needing to read host certs as root 
			// wants_condor_priv = true;
	} else if( stricmp(name_in_config_file,"CONDOR_VIEW") == 0 ||
			   stricmp(name_in_config_file,"VIEW_SERVER") == 0 ) {
		Daemon d( DT_VIEW_COLLECTOR );
		command_port = d.port();
			// We can't do this b/c of needing to read host certs as root 
			// wants_condor_priv = true;
	} 
	else if( strcmp(name_in_config_file,"NEGOTIATOR") == 0 ) {
		char* host = getCmHostFromConfig( "NEGOTIATOR" );
		if( host ) {
			free (host);
			Daemon d( DT_NEGOTIATOR );
			command_port = d.port();
			// We can't do this b/c of needing to read host certs as root 
			// wants_condor_priv = true;
		}
	}

	priv_state priv_mode = PRIV_ROOT;
	
	snprintf(buf, sizeof(buf), "%s_USERID",name_in_config_file);
	char * username = param( buf );
	if(username) {
		// domain is set to NULL since we don't care about the domain
		// unless we're on Windows, and on Windows the master
		// always runs as SYSTEM (as a service). --stolley
		int result = init_user_ids(username, NULL);
		if(result) {
			priv_mode = PRIV_USER_FINAL;
		} else {
			dprintf(D_ALWAYS,"couldn't switch to user %s!\n",username);
		}
		free(username);
	}
	if( wants_condor_priv && priv_mode == PRIV_ROOT ) {

#ifndef	WIN32

			// we go this route on UNIX b/c it's safer, easier, and it
			// automatically gets it right if CONDOR_IDS is defined.
		uid_t cuid = get_condor_uid();
		gid_t cgid = get_condor_gid();
		int result = set_user_ids( cuid, cgid );
		if( result ) { 
			priv_mode = PRIV_USER_FINAL;
		} else {
			dprintf( D_ALWAYS, 
					 "couldn't switch to \"condor\" user %d.%d!\n",
					 cuid, cgid );
		}

#else /* WIN32 */

			// don't know what to do here just yet...

#endif /* WIN32 */
	}

	args.AppendArg(shortname);
	if(isDC) {
		args.AppendArg("-f");
	}

	snprintf( buf, sizeof( buf ), "%s_ARGS", name_in_config_file );
	char *daemon_args = param( buf );

	MyString args_error;
	if(!args.AppendArgsV1RawOrV2Quoted(daemon_args,&args_error)) {
		dprintf(D_ALWAYS,"ERROR: failed to parse %s daemon arguments: %s\n",
				buf,
				args_error.Value());
		Restart();
		free(daemon_args);
		return 0;
	}

	free(daemon_args);

    // The below chunk is for HAD support

    // take command port from arguments( buf )
    // Don't mess with buf or tmp (they are not our variables) -
    // allocate them again
    if ( isDC ) {
		int i;
		for(i=0;i<args.Count();i++) {
			char const *cur_arg = args.GetArg(i);
			if(strcmp( cur_arg, "-p" ) == 0 ) {
				if(i+1<args.Count()) {
					char const *port_arg = args.GetArg(i+1);
					command_port = atoi(port_arg);
				}
			}
		}
    }

	// set up a FamilyInfo structure so Daemon Core registers a process family
	// for this daemon with the procd
	//
	FamilyInfo fi;
	fi.max_snapshot_interval = param_integer("PID_SNAPSHOT_INTERVAL", 60);

	pid = daemonCore->Create_Process(
				process_name,	// program to exec
				args,			// args
				priv_mode,		// privledge level
				1,				// which reaper ID to use; use default reaper
				command_port,	// port to use for command port; TRUE=choose one dynamically
				&env,			// environment
				NULL,			// current working directory
				&fi);			// we want a new process family

	if ( pid == FALSE ) {
		// Create_Process failed!
		dprintf( D_FAILURE|D_ALWAYS,
				 "ERROR: Create_Process failed trying to start %s\n",
				 process_name);
		pid = 0;
		// Schedule to try and start it a litle later
		Restart();
		return 0;
	}

	// if this is a restart, start recover timer
	if (restarts > 0) {
		recover_tid = daemonCore->Register_Timer( m_recover_time,
						(TimerHandlercpp)&daemon::Recover,
						"daemon::Recover()", this );
		dprintf(D_FULLDEBUG, "start recover timer (%d)\n", recover_tid);
	}

	const char	*proc_type = command_port ? "DaemonCore " : "";
	if ( DebugFlags & D_FULLDEBUG ) {
		MyString	 args_string, tmp;
		args.GetArgsStringForDisplay( &tmp, 1 );
		if( tmp.Length() ) {
			args_string  = " ";
			args_string += tmp;
		}
		else {
			args_string = tmp;
		}
		dprintf( D_FAILURE|D_ALWAYS,
				 "Started %sprocess \"%s%s\", pid and pgroup = %d\n",
				 proc_type, process_name, args_string.Value(), pid );
	}
	else {
		dprintf( D_FAILURE|D_ALWAYS,
				 "Started %sprocess \"%s\", pid and pgroup = %d\n",
				 proc_type, process_name, pid );
	}
	
		// Make sure we've got the current timestamp for updates, etc.
	timeStamp = GetTimeStamp(watch_name);

		// record the time we were started
	startTime = time(0);

		// Since we just started it, we know it's not a new executable. 
	newExec = FALSE;

		// Be sure to reset the stop_state, if the daemon is HAD
		// managed it will have been set to FAST on startup
	stop_state = NONE;

		// If starting the collector, give it a few seconds to get
		// going before starting other daemons or talking to ti
		// ourselves
{'fraction_non_alphanumeric': 0.1294859813084112, 'fraction_numerical': 0.005771028037383177, 'mean_word_length': 3.176929833121889, 'pattern_counts': {'":': 4, '<': 21, '<?xml version=': 0, '>': 75, 'https://': 0, 'lorem ipsum': 0, 'www.': 2, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '4108365', 'n_tokens_mistral': 16955, 'n_tokens_neox': 15018, 'n_words': 4524}