/* CROND & CRONTAB: (c) Kees Lemmens, Netherlands; June 1993.

	Programs for ATARI ST (running under MINT) to make it possible
	to run background jobs at regular intervals.

	version 1.1: Okt 1993

	- Spawned jobs get uid and gid from user who submitted them.
	- Default directory is changed to users homedir.
	  (again on special request by Jeroen Berger !)

	Any questions or suggestions about this program can be send to:
	lemmens@dv.twi.tudelft.nl
*/

#include "cron.h"

#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <ctype.h>
#include <time.h>
#include <process.h>
#include <signal.h>
#include <pwd.h>
#include <sys\wait.h>

extern void LogMsg(char *name, int pid, char *fmt,...);
extern char *ux2dos(char *);

char *environ[] = {	"", NULL };
/* Environment is not read by Mintshel so it's in fact useless ! */

int sspawnve(int mode,char *cmd, char *argstring, char **envp)
{	char TosArgs[128] = "x";
	int imode;
	
#ifdef DEBUG
	mode = P_WAIT;
#endif

	ux2dos(cmd);
	ux2dos(argstring);

	strncat(TosArgs,argstring,sizeof(TosArgs) - 1);
	*TosArgs = strlen(argstring);	/* first byte is strlen */

	switch(mode)
	{	case P_WAIT    : imode=  0; break;
		case P_NOWAIT  : imode=100; break;
		case P_OVERLAY : imode=200; break;
	}
	
	return (int)Pexec(imode, cmd, TosArgs, (void *)envp);
}

void WriteMailHeader(entry *job,FILE *fp)
{	time_t t;

	time(&t);
	
	/* first line conform mail headers under UNIX */

	fprintf(fp,"From cron %s",ctime(&t));
	fprintf(fp,"To: %s\n",job->User);
	fprintf(fp,"Subject: Output from %s job\n\n",
		job->Type == CRON ? "cron" : "at");
	fprintf(fp,"Command: %s\n\n",job->Command);

	fflush(fp);
}

int StartJob(entry *job,active *ajob)
{	char cmdstring[MAX_TEMPSTR];
	static counter = 0;	/* for unique filenames */
	struct passwd *pwent;
	FILE *fp;
	int  fd;

/* Use a unique filename for output. Tmpnam can't be used, as it 
   also generates $ signs that can confuse the shell !!
*/

#ifdef DEBUG
	strcpy(ajob->Output,"u:/dev/tty");
#else	
	sprintf(ajob->Output,SPOOLDIR "/%08.8d.crn",counter++);
#endif

	strcpy(ajob->User,job->User);
	sprintf(cmdstring,"-c %s; %.*s",DEFPATH,MAX_COMMAND,job->Command);

	if ((fp = fopen(ux2dos(ajob->Output),"w")) == NULL)
		return EROUTP;

	WriteMailHeader(job,fp);

	/* redirect stdout and stderr */

	fd=fileno(fp);
	Fforce(1,fd); Fforce(2,fd);

	{	if((ajob->Pid =(int)fork()) == 0)
		{
			/* must change uid & gid in a subprocess or it will
			   be impossible to change back to uid 0 again !
			*/

			if((pwent = getpwnam(job->User)) != NULL)
			{	(void)setuid(pwent->pw_uid);
				(void)setgid(pwent->pw_gid);

				Dsetpath(ux2dos(pwent->pw_dir));
				/* this assumes we're in drive u: */
			}

			sspawnve(P_OVERLAY,SHELLCMD,cmdstring,environ);

			exit(1); /* just in case Pexec overlay fails */
		}
	}

	if(ajob->Pid < 0)
	{	Fforce(1,0); Fforce(2,0);
		fclose(fp);
		return ERJOB;
	}
	ajob->Status = BUSY;

	Fforce(1,0); Fforce(2,0);
	fclose(fp);
	return ajob->Pid;
}

void HandleExits(active ajobs[])
{	int nr;
	FILE *fp;
	char cmdstring[MAX_COMMAND];
	union
	{	long l;
		struct { int Pid; int ExitCode; } c;
	} stat;

	/* collect all finished jobs */
	 
	while((stat.l=Pwait3(WNOHANG,NULL)) > 0L)
	{
		for(nr=0;nr<MAX_SIMJOBS;nr++)
		{	if(ajobs[nr].Pid == stat.c.Pid && ajobs[nr].Status == BUSY)
			{
				LogMsg(PROGNAME,stat.c.Pid,"Job finished (exit=%d)",
					stat.c.ExitCode);

				/* append exitcode to mail message */

				if ((fp = fopen(ux2dos(ajobs[nr].Output),"a")) != NULL)
				{	fprintf(fp,"\nExit code : %d\n",stat.c.ExitCode);
					fclose(fp);
				}
				else
					LogMsg(PROGNAME,stat.c.Pid,"Can't append to output %s",
						ajobs[nr].Output);

				ajobs[nr].Status = FINISHED;
			}
		}
	}

	/* send output from finished jobs as mail to the user */

#ifndef DEBUG

	for(nr=0;nr<MAX_SIMJOBS;nr++)
	{
		if(ajobs[nr].Status != FINISHED)
			continue;
		
		sprintf(cmdstring,"-c " MAILCMD " " MAILARG,
			ajobs[nr].Output,ajobs[nr].User);

		sspawnve(P_WAIT,SHELLCMD,cmdstring,environ);

		/* and AFTER that (!!) remove the outputfile */
				
		if(remove(ajobs[nr].Output) != 0)
			LogMsg(PROGNAME,stat.c.Pid,"Can't remove output %s",
				ajobs[nr].Output);

		ajobs[nr].Status = FREE;	/* clear status */
	}
#endif
}

void RemoveAtJob(entry *job)
{	char atfile[MAX_FNAME];

	sprintf(atfile,"%s/%s.%03d",ATDIR,job->User,job->AtId);
	if(remove(ux2dos(atfile)) < 0)
		LogMsg(PROGNAME, getpid(), "Can't remove %s",atfile);
}

void DoJobs(entry joblist[], int jobcount, active ajoblist[])
{	int x,n,chpid,cmd;
	int cronout;
	char *message;

	for(x=0;x<jobcount;x++)
	{
		if(joblist[x].Status == START)
		{
			for(n=0;n<MAX_SIMJOBS && ajoblist[n].Status != FREE;n++);

			/* If no more jobs then don't clear job Status so we'll
			   retry next run.
			*/

			if(n>=MAX_SIMJOBS)
			{	LogMsg(PROGNAME,getpid(),"No more active jobs \"%.10s\" (%s)",
					joblist[n].Command,joblist[n].User);
				return;
			}

			chpid = StartJob(&joblist[x],&ajoblist[n]);

			switch(chpid)
			{	case EROUTP:
					message="%Job \".15s\" (%s) no output";
				break;
				case ERJOB:
					message="%Job \".15s\" (%s) failed";
				break;
				default:
					joblist[x].Status = SLEEP;
					switch(joblist[x].Type)
					{	case CRON :
							message="CRON job \"%.10s\" (%s) started";
						break;
						case AT   :
							message="AT job \"%.10s\" (%s) started";

							/* job can be removed: only one run */
							if((cronout=open(CRONPIPE,O_WRONLY)) < 0)
								LogMsg(PROGNAME,getpid(),
									"Internal write to pipe failed");
							else
							{	cmd = INTERNBUILD;
								write(cronout,&cmd,1);
								close(cronout);
							}
							RemoveAtJob(&joblist[x]);
						break;
					}
				break;
			}
			LogMsg(PROGNAME,chpid,message,joblist[x].Command,
				joblist[x].User);

#ifdef DEBUG
			DebugPrintTimes(&joblist[x]);
#endif
			
		}
	}
}