text:
#include "condor_common.h"
#include "baseshadow.h"
#include "condor_classad.h"      // for ClassAds.
#include "condor_qmgr.h"         // have to talk to schedd's Q manager
#include "condor_attributes.h"   // for ATTR_ ClassAd stuff
#include "condor_config.h"       // for param()
#include "condor_email.h"        // for (you guessed it) email stuff
#include "condor_version.h"
#include "condor_ver_info.h"
#include "enum_utils.h"
#include "condor_holdcodes.h"
#include "classad_helpers.h"
#include "classad_merge.h"
#include "dc_startd.h"
#include <math.h>

// these are declared static in baseshadow.h; allocate space here
WriteUserLog BaseShadow::uLog;
BaseShadow* BaseShadow::myshadow_ptr = NULL;

// this appears at the bottom of this file:
//extern "C" int display_dprintf_header(char **buf,int *bufpos,int *buflen);
int display_dprintf_header(char **buf,int *bufpos,int *buflen);
extern bool sendUpdatesToSchedd;

// some helper functions
int getJobAdExitCode(ClassAd *jad, int &exit_code);
int getJobAdExitedBySignal(ClassAd *jad, int &exited_by_signal);
int getJobAdExitSignal(ClassAd *jad, int &exit_signal);

BaseShadow::BaseShadow() {
	spool = NULL;
	fsDomain = uidDomain = NULL;
	jobAd = NULL;
	remove_requested = false;
	cluster = proc = -1;
	gjid = NULL;
	core_file_name = NULL;
	scheddAddr = NULL;
	job_updater = NULL;
	ASSERT( !myshadow_ptr );	// make cetain we're only instantiated once
	myshadow_ptr = this;
	exception_already_logged = false;
	began_execution = FALSE;
	reconnect_e_factor = 0.0;
	reconnect_ceiling = 300;
	prev_run_bytes_sent = 0.0;
	prev_run_bytes_recvd = 0.0;
	m_num_cleanup_retries = 0;
	m_max_cleanup_retries = 5;
	m_lazy_queue_update = true;
	m_cleanup_retry_tid = -1;
	m_cleanup_retry_delay = 30;
	m_RunAsNobody = false;
}

BaseShadow::~BaseShadow() {
	if (spool) free(spool);
	if (fsDomain) free(fsDomain);
	if (jobAd) FreeJobAd(jobAd);
	if (gjid) free(gjid);
	if (scheddAddr) free(scheddAddr);
	if( job_updater ) delete job_updater;
}

void
BaseShadow::baseInit( ClassAd *job_ad, const char* schedd_addr, const char *xfer_queue_contact_info )
{
	int pending = FALSE;

	if( ! job_ad ) {
		EXCEPT("baseInit() called with NULL job_ad!");
	}
	jobAd = job_ad;

	if (sendUpdatesToSchedd && ! is_valid_sinful(schedd_addr)) {
		EXCEPT("schedd_addr not specified with valid address");
	}
	scheddAddr = sendUpdatesToSchedd ? strdup( schedd_addr ) : strdup("noschedd");

	m_xfer_queue_contact_info = xfer_queue_contact_info;

	if ( !jobAd->LookupString(ATTR_OWNER, owner)) {
		EXCEPT("Job ad doesn't contain an %s attribute.", ATTR_OWNER);
	}

	if( !jobAd->LookupInteger(ATTR_CLUSTER_ID, cluster)) {
		EXCEPT("Job ad doesn't contain an %s attribute.", ATTR_CLUSTER_ID);
	}

	if( !jobAd->LookupInteger(ATTR_PROC_ID, proc)) {
		EXCEPT("Job ad doesn't contain an %s attribute.", ATTR_PROC_ID);
	}

		// Grab the GlobalJobId if we've got it.
	if( ! jobAd->LookupString(ATTR_GLOBAL_JOB_ID, &gjid) ) {
		gjid = NULL;
	}

	// grab the NT domain if we've got it
	jobAd->LookupString(ATTR_NT_DOMAIN, domain);
	if ( !jobAd->LookupString(ATTR_JOB_IWD, iwd)) {
		EXCEPT("Job ad doesn't contain an %s attribute.", ATTR_JOB_IWD);
	}

	if( !jobAd->LookupFloat(ATTR_BYTES_SENT, prev_run_bytes_sent) ) {
		prev_run_bytes_sent = 0;
	}
	if( !jobAd->LookupFloat(ATTR_BYTES_RECVD, prev_run_bytes_recvd) ) {
		prev_run_bytes_recvd = 0;
	}

		// construct the core file name we'd get if we had one.
	MyString tmp_name = iwd;
	tmp_name += DIR_DELIM_CHAR;
	tmp_name += "core.";
	tmp_name += cluster;
	tmp_name += '.';
	tmp_name += proc;
	core_file_name = strdup( tmp_name.Value() );

        // put the shadow's sinful string into the jobAd.  Helpful for
        // the mpi shadow, at least...and a good idea in general.
	MyString tmp_addr = ATTR_MY_ADDRESS;
	tmp_addr += "=\"";
	tmp_addr += daemonCore->InfoCommandSinfulString();
	tmp_addr += '"';
    if ( !jobAd->Insert( tmp_addr.Value() )) {
        EXCEPT( "Failed to insert %s!", ATTR_MY_ADDRESS );
    }

	DebugId = display_dprintf_header;

	config();

		// Make sure we've got enough swap space to run
	checkSwap();

	// handle system calls with Owner's privilege
// XXX this belong here?  We'll see...
	if ( !init_user_ids(owner.Value(), domain.Value())) {
		dprintf(D_ALWAYS, "init_user_ids() failed as user %s\n",owner.Value() );
		// uids.C will EXCEPT when we set_user_priv() now
		// so there's not much we can do at this point

#if ! defined(WIN32)
		if ( param_boolean( "SHADOW_RUN_UNKNOWN_USER_JOBS", false ) )
		{
			dprintf(D_ALWAYS, "trying init_user_ids() as user nobody\n" );

			owner="nobody";
			domain=NULL;
			if (!init_user_ids(owner.Value(), domain.Value()))
			{
				dprintf(D_ALWAYS, "init_user_ids() failed!\n");
			}
			else
			{
				jobAd->Assign( ATTR_JOB_RUNAS_OWNER, "FALSE" );
				m_RunAsNobody=true;
				dprintf(D_ALWAYS, "init_user_ids() now running as user nobody\n");
			}
		}
#endif

	}
	set_user_priv();
	daemonCore->Register_Priv_State( PRIV_USER );

	dumpClassad( "BaseShadow::baseInit()", this->jobAd, D_JOB );

		// initialize the UserPolicy object
	shadow_user_policy.init( jobAd, this );

		// setup an object to keep our job ad updated to the schedd's
		// permanent job queue.  this clears all the dirty bits on our
		// copy of the classad, so anything we touch after this will
		// be updated to the schedd when appropriate.

		// Unless we got a command line arg asking us not to
	if (sendUpdatesToSchedd) {
		// the usual case
		job_updater = new QmgrJobUpdater( jobAd, scheddAddr, CondorVersion() );
	} else {
		job_updater = new NullQmgrJobUpdater( jobAd, scheddAddr, CondorVersion() );
	}

		// init user log; hold on failure
		// NOTE: job_updater must be initialized _before_ initUserLog(),
		// in order to handle the case of the job going on hold as a
		// result of failure in initUserLog().
	initUserLog();

		// change directory; hold on failure
	if ( cdToIwd() == -1 ) {
		EXCEPT("Could not cd to initial working directory");
	}

		// check to see if this invocation of the shadow is just to write
		// a terminate event and exit since this job had been recorded as
		// pending termination, but somehow the job didn't leave the queue
		// and the schedd is trying to restart it again..
	if( jobAd->LookupInteger(ATTR_TERMINATION_PENDING, pending)) {
		if (pending == TRUE) {
			// If the classad of this job "thinks" that this job should be
			// finished already, let's enact that belief.
			// This function does not return.
			this->terminateJob(US_TERMINATE_PENDING);
		}
	}

		// If we need to claim the startd before activating the claim
	int wantClaiming = 0;
	jobAd->LookupBool(ATTR_CLAIM_STARTD, wantClaiming);
	if (wantClaiming) {
		MyString startdSinful;
		MyString claimid;

			// Pull startd addr and claimid out of the jobad
		jobAd->LookupString(ATTR_STARTD_IP_ADDR, startdSinful);
		jobAd->LookupString(ATTR_CLAIM_ID, claimid);

		dprintf(D_ALWAYS, "%s is true, trying to claim startd %s\n", ATTR_CLAIM_STARTD, startdSinful.Value());

		classy_counted_ptr<DCStartd> startd = new DCStartd("description", NULL, startdSinful.Value(), claimid.Value());

		classy_counted_ptr<DCMsgCallback> cb =
			new DCMsgCallback((DCMsgCallback::CppFunction)&BaseShadow::startdClaimedCB,
			this, jobAd);

			// this can't fail, will always call the callback
		startd->asyncRequestOpportunisticClaim(jobAd,
											   "description",
											   daemonCore->InfoCommandSinfulString(),
											   1200 /*alive interval*/,
											   20 /* net timeout*/,
											   100 /*total timeout*/,
											   cb);
	}
}

	// We land in this callback when we need to claim the startd
	// when we get here, the claiming is finished, successful
	// or not
void BaseShadow::startdClaimedCB(DCMsgCallback *) {

	// We've claimed the startd, the following kicks off the
	// activation of the claim, and runs the job
	this->spawn();
}

void BaseShadow::config()
{
	if (spool) free(spool);
	spool = param("SPOOL");
	if (!spool) {
		EXCEPT("SPOOL not specified in config file.");
	}

	if (fsDomain) free(fsDomain);
	fsDomain = param( "FILESYSTEM_DOMAIN" );
	if (!fsDomain) {
		EXCEPT("FILESYSTEM_DOMAIN not specified in config file.");
	}

	if (uidDomain) free(uidDomain);
	uidDomain = param( "UID_DOMAIN" );
	if (!uidDomain) {
		EXCEPT("UID_DOMAIN not specified in config file.");
	}

	reconnect_ceiling = param_integer( "RECONNECT_BACKOFF_CEILING", 300 );

	reconnect_e_factor = 0.0;
	reconnect_e_factor = param_double( "RECONNECT_BACKOFF_FACTOR", 2.0, 0.0 );
	if( reconnect_e_factor < -1e-4 || reconnect_e_factor > 1e-4) {
    	reconnect_e_factor = 2.0;
    }

	m_cleanup_retry_tid = -1;
	m_num_cleanup_retries = 0;
		// NOTE: these config knobs are very similar to
		// LOCAL_UNIVERSE_MAX_JOB_CLEANUP_RETRIES and
		// LOCAL_UNIVERSE_JOB_CLEANUP_RETRY_DELAY in the local starter.
	m_max_cleanup_retries = param_integer("SHADOW_MAX_JOB_CLEANUP_RETRIES", 5);
	m_cleanup_retry_delay = param_integer("SHADOW_JOB_CLEANUP_RETRY_DELAY", 30);

	m_lazy_queue_update = param_boolean("SHADOW_LAZY_QUEUE_UPDATE", true);
}

int BaseShadow::cdToIwd() {
	int iRet =0;

#if ! defined(WIN32)
	priv_state p = PRIV_UNKNOWN;

	if (m_RunAsNobody)
		p = set_root_priv();
#endif

	if (chdir(iwd.Value()) < 0) {
		int chdir_errno = errno;
		dprintf(D_ALWAYS, "\n\nPath does not exist.\n"
				"He who travels without bounds\n"
				"Can't locate data.\n\n" );
		MyString hold_reason;
		hold_reason.formatstr("Cannot access initial working directory %s: %s",
		                    iwd.Value(), strerror(chdir_errno));
		dprintf( D_ALWAYS, "%s\n",hold_reason.Value());
		holdJobAndExit(hold_reason.Value(),CONDOR_HOLD_CODE_IwdError,chdir_errno);
		iRet = -1;
	}

#if ! defined(WIN32)
	if ( m_RunAsNobody )
		set_priv(p);
#endif

	return iRet;
}

void
BaseShadow::shutDown( int reason )
{
		// exit now if there is no job ad
	if ( !getJobAd() ) {
		DC_Exit( reason );
	}

		// if we are being called from the exception handler, return
		// now to prevent infinite loop in case we call EXCEPT below.
	if ( reason == JOB_EXCEPTION ) {
		return;
	}

		// Only if the job is trying to leave the queue should we
		// evaluate the user job policy...
	if( reason == JOB_EXITED || reason == JOB_COREDUMPED ) {
		if( !waitingToUpdateSchedd() ) {
			shadow_user_policy.checkAtExit();
				// WARNING: 'this' may have been deleted by the time we get here!!!
		}
	}
	else {
		// if we aren't trying to evaluate the user's policy, we just
		// want to evict this job.
		evictJob( reason );
	}
}

int
BaseShadow::nextReconnectDelay( int attempts )
{
	if( ! attempts ) {
			// first time, do it right away
		return 0;
	}
	int n = (int)ceil(::pow(reconnect_e_factor, (attempts+2)));
	if( n > reconnect_ceiling || n < 0 ) {
		n = reconnect_ceiling;
	}
	return n;
}

void
BaseShadow::reconnectFailed( const char* reason )
{
		// try one last time to release the claim, write a UserLog event
		// about it, and exit with a special status.
	dprintf( D_ALWAYS, "Reconnect FAILED: %s\n", reason );

	logReconnectFailedEvent( reason );

		// does not return
	DC_Exit( JOB_SHOULD_REQUEUE );
}

void
BaseShadow::holdJob( const char* reason, int hold_reason_code, int hold_reason_subcode )
{
	dprintf( D_ALWAYS, "Job %d.%d going into Hold state (code %d,%d): %s\n",
			 getCluster(), getProc(), hold_reason_code, hold_reason_subcode,reason );

	if( ! jobAd ) {
		dprintf( D_ALWAYS, "In HoldJob() w/ NULL JobAd!" );
		DC_Exit( JOB_SHOULD_HOLD );
	}

		// cleanup this shadow (kill starters, etc)
	cleanUp();

		// Put the reason in our job ad.
	jobAd->Assign( ATTR_HOLD_REASON, reason );
	jobAd->Assign( ATTR_HOLD_REASON_CODE, hold_reason_code );
	jobAd->Assign( ATTR_HOLD_REASON_SUBCODE, hold_reason_subcode );

		// try to send email (if the user wants it)
	emailHoldEvent( reason );

		// update the job queue for the attributes we care about
	if( !updateJobInQueue(U_HOLD) ) {
			// trouble!  TODO: should we do anything else?
		dprintf( D_ALWAYS, "Failed to update job queue!\n" );
	}

}

void
BaseShadow::holdJobAndExit( const char* reason, int hold_reason_code, int hold_reason_subcode )
{
	holdJob(reason,hold_reason_code,hold_reason_subcode);

	// finally, exit and tell the schedd what to do
	DC_Exit( JOB_SHOULD_HOLD );
}

void
BaseShadow::mockTerminateJob( MyString exit_reason,
		bool exited_by_signal, int exit_code, int exit_signal,
		bool core_dumped )
{
	if (exit_reason == "") {
		exit_reason = "Exited normally";
	}

	dprintf( D_ALWAYS, "Mock terminating job %d.%d: "
			"exited_by_signal=%s, exit_code=%d OR exit_signal=%d, "
			"core_dumped=%s, exit_reason=\"%s\"\n",
			 getCluster(),
			 getProc(),
			 exited_by_signal ? "TRUE" : "FALSE",
			 exit_code,
			 exit_signal,
			 core_dumped ? "TRUE" : "FALSE",
			 exit_reason.Value());

	if( ! jobAd ) {
		dprintf(D_ALWAYS, "BaseShadow::mockTerminateJob(): NULL JobAd! "
			"Holding Job!");
		DC_Exit( JOB_SHOULD_HOLD );
	}

	// Insert the various exit attributes into our job ad.
	jobAd->Assign( ATTR_JOB_CORE_DUMPED, core_dumped );
	jobAd->Assign( ATTR_ON_EXIT_BY_SIGNAL, exited_by_signal );

	if (exited_by_signal) {
		jobAd->Assign( ATTR_ON_EXIT_SIGNAL, exit_signal );
	} else {
		jobAd->Assign( ATTR_ON_EXIT_CODE, exit_code );
	}

	jobAd->Assign( ATTR_EXIT_REASON, exit_reason );

		// update the job queue for the attributes we care about
	if( !updateJobInQueue(U_TERMINATE) ) {
			// trouble!  TODO: should we do anything else?
		dprintf( D_ALWAYS, "Failed to update job queue!\n" );
	}
}

void BaseShadow::removeJobPre( const char* reason )
{
	if( ! jobAd ) {
		dprintf( D_ALWAYS, "In removeJob() w/ NULL JobAd!" );
	}
	dprintf( D_ALWAYS, "Job %d.%d is being removed: %s\n",
			 getCluster(), getProc(), reason );

	// cleanup this shadow (kill starters, etc)
	cleanUp();

	// Put the reason in our job ad.
	int size = strlen( reason ) + strlen( ATTR_REMOVE_REASON ) + 4;
	char* buf = (char*)malloc( size * sizeof(char) );
	if( ! buf ) {
		EXCEPT( "Out of memory!" );
	}
	sprintf( buf, "%s=\"%s\"", ATTR_REMOVE_REASON, reason );
	jobAd->Insert( buf );
	free( buf );

	emailRemoveEvent( reason );

	// update the job ad in the queue with some important final
	// attributes so we know what happened to the job when using
	// condor_history...
	if( !updateJobInQueue(U_REMOVE) ) {
			// trouble!  TODO: should we do anything else?
		dprintf( D_ALWAYS, "Failed to update job queue!\n" );
	}
}

void BaseShadow::removeJob( const char* reason )
{
	this->removeJobPre(reason);

	// does not return.
	DC_Exit( JOB_SHOULD_REMOVE );
}

void
BaseShadow::retryJobCleanup( void )
{
	m_num_cleanup_retries++;
	if (m_num_cleanup_retries > m_max_cleanup_retries) {
		dprintf(D_ALWAYS,
		        "Maximum number of job cleanup retry attempts "
		        "(SHADOW_MAX_JOB_CLEANUP_RETRIES=%d) reached"
		        "; Forcing job requeue!\n",
		        m_max_cleanup_retries);
		DC_Exit(JOB_SHOULD_REQUEUE);
	}
	ASSERT(m_cleanup_retry_tid == -1);
	m_cleanup_retry_tid = daemonCore->Register_Timer(m_cleanup_retry_delay, 0,
					(TimerHandlercpp)&BaseShadow::retryJobCleanupHandler,
					"retry job cleanup", this);
	dprintf(D_FULLDEBUG, "Will retry job cleanup in "
	        "SHADOW_JOB_CLEANUP_RETRY_DELAY=%d seconds\n",
	        m_cleanup_retry_delay);
}

int
BaseShadow::retryJobCleanupHandler( void )
{
	m_cleanup_retry_tid = -1;
	dprintf(D_ALWAYS, "Retrying job cleanup, calling terminateJob()\n");
	terminateJob();
	return TRUE;
}

void
BaseShadow::terminateJob( update_style_t kind ) // has a default argument of US_NORMAL
{
	int reason;
	bool signaled;
	MyString str;

	if( ! jobAd ) {
		dprintf( D_ALWAYS, "In terminateJob() w/ NULL JobAd!" );
	}

	/* The first thing we do is record that we are in a termination pending
		state. */
	if (kind == US_NORMAL) {
		str.formatstr("%s = TRUE", ATTR_TERMINATION_PENDING);
		jobAd->Insert(str.Value());
	}

	if (kind == US_TERMINATE_PENDING) {
		// In this case, the job had already completed once and the
		// status had been saved to the job queue, however, for
		// some reason, the shadow didn't exit with a good value and
		// the job had been requeued. When this style of update
		// is used, it is a shortcut from the very birth of the shadow
		// to here, and so there will not be a remote resource or
		// anything like that set up. In this situation, we just
		// want to write the log event and mail the user and exit
		// with a good exit code so the schedd removes the job from
		// the queue. If for some reason the logging fails once again,
		// the process continues to repeat.
		// This means at least once semantics for the termination event
		// and user email, but at no time should the job actually execute
		// again.

		int exited_by_signal = FALSE;
		int exit_signal = 0;
		int exit_code = 0;

		getJobAdExitedBySignal(jobAd, exited_by_signal);
		if (exited_by_signal == TRUE) {
			getJobAdExitSignal(jobAd, exit_signal);
		} else {
			getJobAdExitCode(jobAd, exit_code);
		}

		if (exited_by_signal == TRUE) {
			reason = JOB_COREDUMPED;
			str.formatstr("%s = \"%s\"", ATTR_JOB_CORE_FILENAME, core_file_name);
			jobAd->Insert(str.Value());
		} else {
			reason = JOB_EXITED;
		}

		dprintf( D_ALWAYS, "Job %d.%d terminated: %s %d\n",
	 		getCluster(), getProc(),
	 		exited_by_signal? "killed by signal" : "exited with status",
	 		exited_by_signal ? exit_signal : exit_code );

			// write stuff to user log, but get values from jobad
		logTerminateEvent( reason, kind );

			// email the user, but get values from jobad
		emailTerminateEvent( reason, kind );

		DC_Exit( reason );
	}

	// the default path when kind == US_NORMAL

	// cleanup this shadow (kill starters, etc)
	cleanUp();

	reason = getExitReason();
	signaled = exitedBySignal();

	/* also store the corefilename into the jobad so we can recover this
		during a termination pending scenario. */
	if( reason == JOB_COREDUMPED ) {
		str.formatstr("%s = \"%s\"", ATTR_JOB_CORE_FILENAME, getCoreName());
		jobAd->Insert(str.Value());
	}

    // Update final Job committed time
    int last_ckpt_time = 0;
    jobAd->LookupInteger(ATTR_LAST_CKPT_TIME, last_ckpt_time);
    int current_start_time = 0;
    jobAd->LookupInteger(ATTR_JOB_CURRENT_START_DATE, current_start_time);
    int int_value = (last_ckpt_time > current_start_time) ?
                        last_ckpt_time : current_start_time;

    if( int_value > 0 ) {
        int job_committed_time = 0;
        jobAd->LookupInteger(ATTR_JOB_COMMITTED_TIME, job_committed_time);
		int delta = (int)time(NULL) - int_value;
        job_committed_time += delta;
        jobAd->Assign(ATTR_JOB_COMMITTED_TIME, job_committed_time);

		float slot_weight = 1;
		jobAd->LookupFloat(ATTR_JOB_MACHINE_ATTR_SLOT_WEIGHT0, slot_weight);
		float slot_time = 0;
		jobAd->LookupFloat(ATTR_COMMITTED_SLOT_TIME, slot_time);
		slot_time += slot_weight * delta;
		jobAd->Assign(ATTR_COMMITTED_SLOT_TIME, slot_time);
    }

	CommitSuspensionTime(jobAd);

	// update the job ad in the queue with some important final
	// attributes so we know what happened to the job when using
	// condor_history...
    if (m_num_cleanup_retries < 1 &&
        param_boolean("SHADOW_TEST_JOB_CLEANUP_RETRY", false)) {
		dprintf( D_ALWAYS,
				 "Testing Failure to perform final update to job queue!\n");
		retryJobCleanup();
		return;
    }
	if( !updateJobInQueue(U_TERMINATE) ) {
		dprintf( D_ALWAYS,
				 "Failed to perform final update to job queue!\n");
		retryJobCleanup();
		return;
	}

	// Let's maximize the effectiveness of that queue synchronization and
	// only record the job as done if the update to the queue was successful.
	// If any of these next operations fail and the shadow exits with an
	// exit code which causes the job to get requeued, it will be in the
	// "terminate pending" state marked by the ATTR_TERMINATION_PENDING
	// attribute.

	dprintf( D_ALWAYS, "Job %d.%d terminated: %s %d\n",
	 	getCluster(), getProc(),
	 	signaled ? "killed by signal" : "exited with status",
	 	signaled ? exitSignal() : exitCode() );

	// write stuff to user log:
	logTerminateEvent( reason );

	// email the user
	emailTerminateEvent( reason );

	if( reason == JOB_EXITED && claimIsClosing() ) {
			// Startd not accepting any more jobs on this claim.
			// We do this here to avoid having to treat this case
			// identically to JOB_EXITED in the code leading up to
			// this point.
		dprintf(D_FULLDEBUG,"Startd is closing claim, so no more jobs can be run on it.\n");
		reason = JOB_EXITED_AND_CLAIM_CLOSING;
	}

	// try to get a new job for this shadow
	if( recycleShadow(reason) ) {
		// recycleShadow delete's this, so we must return immediately
		return;
	}

	// does not return.
	DC_Exit( reason );
}

void
BaseShadow::evictJob( int reason )
{
	MyString from_where;
	MyString machine;
	if( getMachineName(machine) ) {
		from_where.formatstr(" from %s",machine.Value());
	}
	dprintf( D_ALWAYS, "Job %d.%d is being evicted%s\n",
			 getCluster(), getProc(), from_where.Value() );

	if( ! jobAd ) {
		dprintf( D_ALWAYS, "In evictJob() w/ NULL JobAd!" );
		DC_Exit( reason );
	}

		// cleanup this shadow (kill starters, etc)
	cleanUp();

		// write stuff to user log:
	logEvictEvent( reason );

		// record the time we were vacated into the job ad
	char buf[64];
	sprintf( buf, "%s = %d", ATTR_LAST_VACATE_TIME, (int)time(0) );
	jobAd->Insert( buf );

		// update the job ad in the queue with some important final
		// attributes so we know what happened to the job when using
		// condor_history...
	if( !updateJobInQueue(U_EVICT) ) {
			// trouble!  TODO: should we do anything else?
		dprintf( D_ALWAYS, "Failed to update job queue!\n" );
	}

		// does not return.
	DC_Exit( reason );
}

void
BaseShadow::requeueJob( const char* reason )
{
	if( ! jobAd ) {
		dprintf( D_ALWAYS, "In requeueJob() w/ NULL JobAd!" );
	}
	dprintf( D_ALWAYS,
			 "Job %d.%d is being put back in the job queue: %s\n",
			 getCluster(), getProc(), reason );

		// cleanup this shadow (kill starters, etc)
	cleanUp();

		// Put the reason in our job ad.
	int size = strlen( reason ) + strlen( ATTR_REQUEUE_REASON ) + 4;
	char* buf = (char*)malloc( size * sizeof(char) );
	if( ! buf ) {
		EXCEPT( "Out of memory!" );
	}
	sprintf( buf, "%s=\"%s\"", ATTR_REQUEUE_REASON, reason );
	jobAd->Insert( buf );
	free( buf );

		// write stuff to user log:
	logRequeueEvent( reason );

		// update the job ad in the queue with some important final
		// attributes so we know what happened to the job when using
		// condor_history...
	if( !updateJobInQueue(U_REQUEUE) ) {
			// trouble!  TODO: should we do anything else?
		dprintf( D_ALWAYS, "Failed to update job queue!\n" );
	}

		// does not return.
	DC_Exit( JOB_SHOULD_REQUEUE );
}

void
BaseShadow::emailHoldEvent( const char* reason )
{
	Email mailer;
	mailer.sendHold( jobAd, reason );
}

void
BaseShadow::emailRemoveEvent( const char* reason )
{
	Email mailer;
	mailer.sendRemove( jobAd, reason );
}

FILE*
BaseShadow::emailUser( const char *subjectline )
{
	dprintf(D_FULLDEBUG, "BaseShadow::emailUser() called.\n");
	if( !jobAd ) {
		return NULL;
	}
	return email_user_open( jobAd, subjectline );
}

void BaseShadow::initUserLog()
{
	MyString logfilename,dagmanLogFile;
	int  use_xml;

		// we expect job_updater to already be initialized, in case we
		// need to put the job on hold as a result of failure to open
		// the log
	ASSERT( job_updater );

	std::vector<const char*> logfiles;
	if ( getPathToUserLog(jobAd, logfilename) ) {
		logfiles.push_back(logfilename.Value());
		dprintf(D_FULLDEBUG, "%s = %s\n", ATTR_ULOG_FILE, logfilename.Value());
	}
	if ( getPathToUserLog(jobAd, dagmanLogFile, ATTR_DAGMAN_WORKFLOW_LOG) ) {
		logfiles.push_back(dagmanLogFile.Value());
		dprintf(D_FULLDEBUG, "%s = %s\n", ATTR_DAGMAN_WORKFLOW_LOG, dagmanLogFile.Value());
	}
	if( !logfiles.empty()) {
		if( !uLog.initialize (owner.Value(), domain.Value(), logfiles,
				cluster, proc, 0, gjid)) {
			MyString hold_reason;
			hold_reason.formatstr("Failed to initialize user log to %s or %s",
				logfilename.Value(), dagmanLogFile.Value());
			dprintf( D_ALWAYS, "%s\n",hold_reason.Value());
			holdJobAndExit(hold_reason.Value(),
					CONDOR_HOLD_CODE_UnableToInitUserLog,0);
				// holdJobAndExit() should not return, but just in case it does
				// EXCEPT
			EXCEPT("Failed to initialize user log: %s",hold_reason.Value());
		}
		uLog.setUseXML(jobAd->LookupBool(ATTR_ULOG_USE_XML, use_xml) &&
			use_xml);
		if(logfiles.size() > 1) {
			MyString msk;
			jobAd->LookupString(ATTR_DAGMAN_WORKFLOW_MASK, msk);
			Tokenize(msk.Value());
			dprintf(D_FULLDEBUG, "Mask is \"%s\"\n", msk.Value());
			while(const char* mask = GetNextToken(",",true)) {
				dprintf(D_FULLDEBUG, "Adding \"%s\" to mask\n",mask);
				uLog.AddToMask(ULogEventNumber(atoi(mask)));
			}
		}
	} else {
		dprintf(D_FULLDEBUG, "no %s found\n", ATTR_ULOG_FILE);
		dprintf(D_FULLDEBUG, "and no %s found\n", ATTR_DAGMAN_WORKFLOW_LOG);
	}
}

// returns TRUE if attribute found.
int getJobAdExitCode(ClassAd *jad, int &exit_code)
{
	if( ! jad->LookupInteger(ATTR_ON_EXIT_CODE, exit_code) ) {
		return FALSE;
	}

	return TRUE;
}

// returns TRUE if attribute found.
int getJobAdExitedBySignal(ClassAd *jad, int &exited_by_signal)
{
	if( ! jad->LookupInteger(ATTR_ON_EXIT_BY_SIGNAL, exited_by_signal) ) {
		return FALSE;
	}

	return TRUE;
}

// returns TRUE if attribute found.
int getJobAdExitSignal(ClassAd *jad, int &exit_signal)
{
	if( ! jad->LookupInteger(ATTR_ON_EXIT_SIGNAL, exit_signal) ) {
		return FALSE;
	}

	return TRUE;
}

static void set_usageAd (ClassAd* jobAd, ClassAd ** ppusageAd)
{
	std::string resslist;
	if ( ! jobAd->LookupString("ProvisionedResources", resslist))
		resslist = "Cpus, Disk, Memory";

	StringList reslist(resslist.c_str());
	if (reslist.number() > 0) {
		ClassAd * puAd = new ClassAd();
		puAd->Clear(); // get rid of default "CurrentTime = time()" value.

		reslist.rewind();
		while (const char * resname = reslist.next()) {
			std::string attr;
			std::string res = resname;
			title_case(res); // capitalize it to make it print pretty.
			const int copy_ok = classad::Value::ERROR_VALUE | classad::Value::BOOLEAN_VALUE | classad::Value::INTEGER_VALUE | classad::Value::REAL_VALUE;
			classad::Value value;
			attr = res + "Provisioned";	 // provisioned value
			if (jobAd->EvalAttr(attr.c_str(), NULL, value) && (value.GetType() & copy_ok) != 0) {
				classad::ExprTree * plit = classad::Literal::MakeLiteral(value);
				if (plit) {
					puAd->Insert(resname, plit); // usage ad has attribs like they appear in Machine ad
				}
			}
			// /*for debugging*/ else { puAd->Assign(resname, 42); }
			attr = "Request"; attr += res;   	// requested value
			if (jobAd->EvalAttr(attr.c_str(), NULL, value)&& (value.GetType() & copy_ok) != 0) {
				classad::ExprTree * plit = classad::Literal::MakeLiteral(value);
				if (plit) {
					puAd->Insert(attr.c_str(), plit);
				}
			}
			// /*for debugging*/ else { puAd->Assign(attr.Value(), 99); }
			attr = res + "Usage"; // usage value
			if (jobAd->EvalAttr(attr.c_str(), NULL, value) && (value.GetType() & copy_ok) != 0) {
				classad::ExprTree * plit = classad::Literal::MakeLiteral(value);
				if (plit) {
					puAd->Insert(attr.c_str(), plit);
				}
			}
		}
		*ppusageAd = puAd;
	}
}

// kind defaults to US_NORMAL.
void
BaseShadow::logTerminateEvent( int exitReason, update_style_t kind )
{
	struct rusage run_remote_rusage;
	JobTerminatedEvent event;
	MyString corefile;

	memset( &run_remote_rusage, 0, sizeof(struct rusage) );

	switch( exitReason ) {
	case JOB_EXITED:
	case JOB_COREDUMPED:
		break;
	default:
		dprintf( D_ALWAYS,
				 "UserLog logTerminateEvent with unknown reason (%d), aborting\n",
				 exitReason );
		return;
	}

	if (kind == US_TERMINATE_PENDING) {

		float float_value;
		int exited_by_signal = FALSE;
		int exit_signal = 0;
		int exit_code = 0;

		getJobAdExitedBySignal(jobAd, exited_by_signal);
		if (exited_by_signal == TRUE) {
			getJobAdExitSignal(jobAd, exit_signal);
			event.normal = false;
			event.signalNumber = exit_signal;
		} else {
			getJobAdExitCode(jobAd, exit_code);
			event.normal = true;
			event.returnValue = exit_code;
		}

		/* grab usage information out of job ad */
		if( jobAd->LookupFloat(ATTR_JOB_REMOTE_SYS_CPU, float_value) ) {
			run_remote_rusage.ru_stime.tv_sec = (int) float_value;
		}

		if( jobAd->LookupFloat(ATTR_JOB_REMOTE_USER_CPU, float_value) ) {
			run_remote_rusage.ru_utime.tv_sec = (int) float_value;
		}

		event.run_remote_rusage = run_remote_rusage;
		event.total_remote_rusage = run_remote_rusage;

		/*
		  we want to log the events from the perspective of the user
		  job, so if the shadow *sent* the bytes, then that means the
		  user job *received* the bytes
		*/
		jobAd->LookupFloat(ATTR_BYTES_SENT, event.recvd_bytes);
		jobAd->LookupFloat(ATTR_BYTES_RECVD, event.sent_bytes);

		event.total_recvd_bytes = event.recvd_bytes;
		event.total_sent_bytes = event.sent_bytes;

		if( exited_by_signal == TRUE ) {
			jobAd->LookupString(ATTR_JOB_CORE_FILENAME, corefile);
			event.setCoreFile( corefile.Value() );
		}

		if (!uLog.writeEvent (&event,jobAd)) {
			dprintf (D_ALWAYS,"Unable to log "
				 	"ULOG_JOB_TERMINATED event\n");
			EXCEPT("UserLog Unable to log ULOG_JOB_TERMINATED event");
		}

		return;
	}

	// the default kind == US_NORMAL path

	run_remote_rusage = getRUsage();

	if( exitedBySignal() ) {
		event.normal = false;
		event.signalNumber = exitSignal();
	} else {
		event.normal = true;
		event.returnValue = exitCode();
	}

		// TODO: fill in local/total rusage
		// event.run_local_rusage = r;
	event.run_remote_rusage = run_remote_rusage;
		// event.total_local_rusage = r;
	event.total_remote_rusage = run_remote_rusage;

		/*
		  we want to log the events from the perspective of the user
		  job, so if the shadow *sent* the bytes, then that means the
		  user job *received* the bytes
		*/
	event.recvd_bytes = bytesSent();
	event.sent_bytes = bytesReceived();

	event.total_recvd_bytes = prev_run_bytes_recvd + bytesSent();
	event.total_sent_bytes = prev_run_bytes_sent + bytesReceived();

	if( exitReason == JOB_COREDUMPED ) {
		event.setCoreFile( core_file_name );
	}

#if 1
	set_usageAd(jobAd, &event.pusageAd);
#else
	std::string resslist;
	if ( ! jobAd->LookupString("PartitionableResources", resslist))
		resslist = "Cpus, Disk, Memory";

	StringList reslist(resslist.c_str());
	if (reslist.number() > 0) {
		int64_t int64_value = 0;
		ClassAd * puAd = new ClassAd();
		puAd->Clear(); // get rid of default "CurrentTime = time()" value.

		reslist.rewind();
		char * resname = NULL;
		while ((resname = reslist.next()) != NULL) {
			MyString attr;
			int64_value = -1;
			attr.formatstr("%s", resname); // provisioned value
			if (jobAd->LookupInteger(attr.Value(), int64_value)) {
				puAd->Assign(resname, int64_value);
			}
			// /*for debugging*/ else { puAd->Assign(resname, 42); }
			int64_value = -2;
			attr.formatstr("Request%s", resname);	// requested value
			if (jobAd->LookupInteger(attr.Value(), int64_value)) {
				puAd->Assign(attr.Value(), int64_value);
			}
			// /*for debugging*/ else { puAd->Assign(attr.Value(), 99); }
			int64_value = -3;
			attr.formatstr("%sUsage", resname); // usage value
			if (jobAd->LookupInteger(attr.Value(), int64_value)) {
				puAd->Assign(attr.Value(), int64_value);
			}
		}
		event.pusageAd = puAd;
	}
#endif

	if (!uLog.writeEvent (&event,jobAd)) {
		dprintf (D_ALWAYS,"Unable to log "
				 "ULOG_JOB_TERMINATED event\n");
		EXCEPT("UserLog Unable to log ULOG_JOB_TERMINATED event");
	}
}

void
BaseShadow::logEvictEvent( int exitReason )
{
	struct rusage run_remote_rusage;
	memset( &run_remote_rusage, 0, sizeof(struct rusage) );

	run_remote_rusage = getRUsage();

	switch( exitReason ) {
	case JOB_CKPTED:
	case JOB_NOT_CKPTED:
	case JOB_KILLED:
		break;
	default:
		dprintf( D_ALWAYS,
				 "logEvictEvent with unknown reason (%d), aborting\n",
				 exitReason );
		return;
	}

	JobEvictedEvent event;
	event.checkpointed = (exitReason == JOB_CKPTED);

		// TODO: fill in local rusage
		// event.run_local_rusage = ???

		// remote rusage
	event.run_remote_rusage = run_remote_rusage;

	set_usageAd(jobAd, &event.pusageAd);

		/*
		  we want to log the events from the perspective of the user
		  job, so if the shadow *sent* the bytes, then that means the
		  user job *received* the bytes
		*/
	event.recvd_bytes = bytesSent();
	event.sent_bytes = bytesReceived();

	if (!uLog.writeEvent (&event,jobAd)) {
		dprintf (D_ALWAYS, "Unable to log ULOG_JOB_EVICTED event\n");
	}
}

void
BaseShadow::logRequeueEvent( const char* reason )
{
	struct rusage run_remote_rusage;
	memset( &run_remote_rusage, 0, sizeof(struct rusage) );

	run_remote_rusage = getRUsage();

	int exit_reason = getExitReason();

	JobEvictedEvent event;

	event.terminate_and_requeued = true;

	if( exitedBySignal() ) {
		event.normal = false;
		event.signal_number = exitSignal();
	} else {
		event.normal = true;
		event.return_value = exitCode();
	}

	if( exit_reason == JOB_COREDUMPED ) {
		event.setCoreFile( core_file_name );
	}

	if( reason ) {
		event.setReason( reason );
	}

		// TODO: fill in local rusage
		// event.run_local_rusage = r;
	event.run_remote_rusage = run_remote_rusage;

		/* we want to log the events from the perspective
		   of the user job, so if the shadow *sent* the
		   bytes, then that means the user job *received*
		   the bytes */
	event.recvd_bytes = bytesSent();
	event.sent_bytes = bytesReceived();

	if (!uLog.writeEvent (&event,jobAd)) {
		dprintf( D_ALWAYS, "Unable to log ULOG_JOB_EVICTED "
				 "(and requeued) event\n" );
	}
}

void
BaseShadow::checkSwap( void )
{
	int	reserved_swap, free_swap;
		// Reserved swap is specified in megabytes
	reserved_swap = param_integer( "RESERVED_SWAP", 0 );
	reserved_swap *= 1024;

	if( reserved_swap == 0 ) {
			// We're not supposed to care about swap space at all, so
			// none of the rest of the checks matter at all.
		return;
	}

	free_swap = sysapi_swap_space();

	dprintf( D_FULLDEBUG, "*** Reserved Swap = %d\n", reserved_swap );
	dprintf( D_FULLDEBUG, "*** Free Swap = %d\n", free_swap );

	if( free_swap < reserved_swap ) {
		dprintf( D_ALWAYS, "Not enough reserved swap space\n" );
		DC_Exit( JOB_NO_MEM );
	}
}

// Note: log_except is static
void
BaseShadow::log_except(const char *msg)
{
	// log shadow exception event
	ShadowExceptionEvent event;
	bool exception_already_logged = false;

	if(!msg) msg = "";
	snprintf(event.message, sizeof(event.message), "%s", msg);
	event.message[sizeof(event.message)-1] = '\0';

	if ( BaseShadow::myshadow_ptr ) {
		BaseShadow *shadow = BaseShadow::myshadow_ptr;

		// we want to log the events from the perspective of the
		// user job, so if the shadow *sent* the bytes, then that
		// means the user job *received* the bytes
		event.recvd_bytes = shadow->bytesSent();
		event.sent_bytes = shadow->bytesReceived();
		exception_already_logged = shadow->exception_already_logged;

		if (shadow->began_execution) {
			event.began_execution = TRUE;
		}

	} else {
		event.recvd_bytes = 0.0;
		event.sent_bytes = 0.0;
	}

	if (!exception_already_logged && !uLog.writeEventNoFsync (&event,NULL))
	{
		::dprintf (D_ALWAYS, "Unable to log ULOG_SHADOW_EXCEPTION event\n");
	}
}

bool
BaseShadow::updateJobAttr( const char *name, const char *expr, bool log )
{
	return job_updater->updateAttr( name, expr, false, log );
}

bool
BaseShadow::updateJobAttr( const char *name, int value, bool log )
{
	return job_updater->updateAttr( name, value, false, log );
}

bool
BaseShadow::updateJobInQueue( update_t type )
{
		// insert the bytes sent/recv'ed by this job into our job ad.
		// we want this from the perspective of the job, so it's
		// backwards from the perspective of the shadow.  if this
		// value hasn't changed, it won't show up as dirty and we
		// won't actually connect to the job queue for it.  we do this
		// here since we want it for all kinds of updates...
	MyString buf;
	buf.formatstr( "%s = %f", ATTR_BYTES_SENT, (prev_run_bytes_sent +
											  bytesReceived()) );
	jobAd->Insert( buf.Value() );
	buf.formatstr( "%s = %f", ATTR_BYTES_RECVD, (prev_run_bytes_recvd +
											   bytesSent()) );
	jobAd->Insert( buf.Value() );

	ASSERT( job_updater );

		// Now that the ad is current, just let our QmgrJobUpdater
		// object take care of the rest...
	return job_updater->updateJob( type );
}

void
BaseShadow::evalPeriodicUserPolicy( void )
{
	shadow_user_policy.checkPeriodic();
}

/**
 * Shared code that should be run once the shadow is sure that
 * everything it's watching over has actually started running.  In the
 * uni-shadow case (vanilla, java, etc) this is called immediately
 * as soon as the starter sends the CONDOR_begin_execution RSC.  For
 * multi-shadow (parallel, MPI), this is invoked once all of the
 * starters have reported in.
 */
void
BaseShadow::resourceBeganExecution( RemoteResource* /* rr */ )
{
		// Set our flag to remember we've really started.
	began_execution = true;

		// Start the timer for the periodic user job policy evaluation.
	shadow_user_policy.startTimer();

		// Start the timer for updating the job queue for this job.
	startQueueUpdateTimer();

		// Update our copy of NumJobStarts, so that the periodic user
		// policy expressions, etc, all have the correct value.
	int job_start_cnt = 0;
	jobAd->LookupInteger(ATTR_NUM_JOB_STARTS, job_start_cnt);
	job_start_cnt++;
	jobAd->Assign(ATTR_NUM_JOB_STARTS, job_start_cnt);
	dprintf(D_FULLDEBUG, "Set %s to %d\n", ATTR_NUM_JOB_STARTS, job_start_cnt);

		// Update NumJobStarts in the schedd.  We honor a config knob
		// for this since there's a big trade-off: if we update
		// agressively and fsync() it to the job queue as soon as we
		// hear from the starter, the semantics are about as solid as
		// we can hope for, but it's a schedd scalability problem.  If
		// we do a lazy update, there's no additional cost to the
		// schedd, but it means that condor_q and quill won't see the
		// change for N minutes, and if we happen to crash during that
		// time, the attribute is never incremented.  However, the
		// semantics aren't 100% solid, even if we don't update lazy,
		// and since the copy in RAM is already updated, all the
		// periodic user policy expressions will work right, so the
		// default is to do it lazy.
	if (m_lazy_queue_update) {
			// For lazy update, we just want to make sure the
			// job_updater object knows about this attribute (which we
			// already updated our copy of).
		job_updater->watchAttribute(ATTR_NUM_JOB_STARTS);
	}
	else {
			// They want it now, so do the qmgmt operation directly.
		updateJobAttr(ATTR_NUM_JOB_STARTS, job_start_cnt);
	}
}

const char*
BaseShadow::getCoreName( void )
{
	if( core_file_name ) {
		return core_file_name;
	}
	return "unknown";
}

void
BaseShadow::startQueueUpdateTimer( void )
{
	job_updater->startUpdateTimer();
}

void
BaseShadow::publishShadowAttrs( ClassAd* ad )
{
	MyString tmp;
	tmp = ATTR_SHADOW_IP_ADDR;
	tmp += "=\"";
	tmp += daemonCore->InfoCommandSinfulString();
    tmp += '"';
	ad->Insert( tmp.Value() );

	tmp = ATTR_SHADOW_VERSION;
	tmp += "=\"";
	tmp += CondorVersion();
    tmp += '"';
	ad->Insert( tmp.Value() );

	char* my_uid_domain = param( "UID_DOMAIN" );
	if( my_uid_domain ) {
		tmp = ATTR_UID_DOMAIN;
		tmp += "=\"";
		tmp += my_uid_domain;
		tmp += '"';
		ad->Insert( tmp.Value() );
		free( my_uid_domain );
	}
}

void BaseShadow::dprintf_va( int flags, const char* fmt, va_list args )
{
		// Print nothing in this version.  A subclass like MPIShadow
		// might like to say ::dprintf( flags, "(res %d)"
	::_condor_dprintf_va( flags, fmt, args );
}

void BaseShadow::dprintf( int flags, const char* fmt, ... )
{
	va_list args;
	va_start( args, fmt );
	this->dprintf_va( flags, fmt, args );
	va_end( args );
}

// This is declared in main.C, and is a pointer to one of the
// various flavors of derived classes of BaseShadow.
// It is only needed for this last function.
extern BaseShadow *Shadow;

// This function is called by dprintf - always display our job, proc,
// and pid in our log entries.
//extern "C"
int
//display_dprintf_header(char **buf,int *bufpos,int *buflen)
display_dprintf_header(char **buf,int *bufpos,int *buflen)
{
	static pid_t mypid = 0;
	int mycluster = -1;
	int myproc = -1;

	if (!mypid) {
		mypid = daemonCore->getpid();
	}

	if (Shadow) {
		mycluster = Shadow->getCluster();
		myproc = Shadow->getProc();
	}

	if ( mycluster != -1 ) {
		return sprintf_realloc( buf, bufpos, buflen, "(%d.%d) (%ld): ", mycluster, myproc, (long)mypid );
	} else {
		return sprintf_realloc( buf, bufpos, buflen, "(?.?) (%ld): ", (long)mypid );
	}

	return 0;
}

bool
BaseShadow::getMachineName( MyString & /*machineName*/ )
{
	return false;
}

void
BaseShadow::CommitSuspensionTime(ClassAd *jobAd)
{
	int uncommitted_suspension_time = 0;
	jobAd->LookupInteger(ATTR_UNCOMMITTED_SUSPENSION_TIME,uncommitted_suspension_time);
	if( uncommitted_suspension_time > 0 ) {
		int committed_suspension_time = 0;
		jobAd->LookupInteger( ATTR_COMMITTED_SUSPENSION_TIME,
							  committed_suspension_time );
		committed_suspension_time += uncommitted_suspension_time;
		jobAd->Assign( ATTR_COMMITTED_SUSPENSION_TIME, committed_suspension_time );
		jobAd->Assign( ATTR_UNCOMMITTED_SUSPENSION_TIME, 0 );
	}
}

int
BaseShadow::handleUpdateJobAd( int sig )
{
	dprintf ( D_FULLDEBUG, "In handleUpdateJobAd, sig %d\n", sig );
	if (!job_updater->retrieveJobUpdates()) {
		dprintf(D_ALWAYS, "Error: Failed to update JobAd\n");
		return -1;
	}

	// Attributes might have changed that would cause the job policy
	// to evaluate differently, so evaluate now.
	shadow_user_policy.checkPeriodic();
	return 0;
}

bool
BaseShadow::jobWantsGracefulRemoval()
{
	bool job_wants_graceful_removal = false;
	ClassAd *thejobAd = getJobAd();
	if( thejobAd ) {
		thejobAd->LookupBool( ATTR_WANT_GRACEFUL_REMOVAL, job_wants_graceful_removal );
	}
	return job_wants_graceful_removal;
}

redpajama_set_name: RedPajamaGithub


text:
Feings est une ancienne commune française située dans le département de Loir-et-Cher en région Centre-Val de Loire.

Au , elle devient une commune déléguée du Controis-en-Sologne.

Géographie

Histoire

Avant 2019

Depuis 2019
Le , la commune fusionne avec Contres, Fougères-sur-Bièvre, Ouchamps et Thenay pour former la commune nouvelle du Controis-en-Sologne dont la création est actée par un arrêté préfectoral du .

Politique et administration

Liste des maires

Liste des maires délégués

Démographie

Évolution démographique

Pyramide des âges
La population de la commune est relativement jeune. Le taux de personnes d'un âge supérieur à 60 ans (19,9 %) est en effet inférieur au taux national (21,6 %) et au taux départemental (26,3 %).
À l'instar des répartitions nationale et départementale, la population féminine de la commune est supérieure à la population masculine. Le taux (50,2 %) est du même ordre de grandeur que le taux national (51,6 %).

La répartition de la population de la commune par tranches d'âge est, en 2007, la suivante :
 49,8 % d'hommes (0 à 14 ans = 24,1 %, 15 à 29 ans = 10,6 %, 30 à 44 ans = 24,8 %, 45 à 59 ans = 20,5 %, plus de 60 ans = 20,1 %) ;
 50,2 % de femmes (0 à 14 ans = 23,2 %, 15 à 29 ans = 15,4 %, 30 à 44 ans = 21,9 %, 45 à 59 ans = 19,9 %, plus de 60 ans = 19,6 %).

Lieux et monuments

Personnalités liées à la commune
Thomas de Mahy de Favras , né au château de Favras, militaire et gentilhomme français et soutien de la cause royaliste durant la révolution française,  pendu à Paris, le .

Notes et références

Notes

Références

Voir aussi

Articles connexes
 Liste des communes de Loir-et-Cher

Liens externes

 Feings sur le site de l'Institut géographique national

Ancienne commune en Loir-et-Cher
Commune déléguée en Loir-et-Cher
Commune en Sologne
Aire urbaine de Blois

redpajama_set_name: RedPajamaWikipedia


The Official Harry Potter Print Series by Olly Moss is Here !
Olly Moss became, in a few years time, a cult artist amongst the community of alternative movie posters lovers. This british artist is indeed the creator of some of Mondo's most amazing prints. From Ghibli to Star Wars, from Evil Dead to the Lord of the Rings, those limited screen prints, long time sold out, are amongst the most collected prints on eBay.
His latest works were in the world of video games, as the lead artist for the beautiful game Firewatch by Campo Santo (believe me you have to play this game).
Well Olly is back on the movie poster scene with this amazing official Harry Potter print series, in collaboration with Pottermore ! Gorgeous artworks based on the vintage style from the old travel posters Olly is so fond of.
For you guys Harry Potter fans, don't lose hope : it is true that the Olly Moss prints are usually sold out in an instant, but in this case, everyone, including me, has a chance to grab one of those beauties. It is indeed a time limited edition, until Monday the 25th of October. It simply means that all the prints ordered during this time will be fulfilled, you will just have to be patient and wait for a few months before receiving your posters.
Now let's go to Olly Moss online store, those giclee prints, 16×24, are all 50 dollars !


The Department of Arid land Agriculture seeks to fill a position at the level of Associate Professor with primary research and secondary teaching responsibilities in the area of Aquaponics (Aquaculture and Hydroponics).
RESPONSIBILITIES The successful candidate must develop a competitive and internationally visible research program in Aquaculture and Hydroponics. The individual will manage, expand and advance aquaponic systems in the college and apply cutting edge developments for establishing a state-of-the-art facility for research, education and training. Capability to develop model systems for industry and small- scale farmers is desirable. Experience in technology transfer and extension activities will be an added advantage. The candidate will develop and teach graduate and undergraduate courses, mentor M.S. and Ph.D. students, and maintain high level of research productivity.