/* CRONTAB: (c) Kees Lemmens, Netherlands; June 1993.

	Program for ATARI ST (running under MINT) to make it possible
	to run background jobs at regular intervals.

	Behaviour is similar to the UNIX crontab command.
	
	The program is only a user interface to the CROND daemon that
	should run on the background of your ATARI to make things work.

	Crontab does 3 things when creating a new crontab :

	- It checks your new crontab for syntax errors.
	- It copies the new crontab to the spooldir, which is often
	  /usr/spool/cron/crontabs.
	- It sends a signal to the cron daemon command pipe to make it
	  reread the crontables from disk.
   
	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 <pwd.h>
#include <errno.h>
#include <signal.h>

extern char *ux2dos(char *string);
extern void LogMsg(char *name, int pid, char *fmt,...);
extern void PokeDaemon(char cmd);

static enum	{unknown, list, delete, replace } Option=unknown;

static void usage(void)
{
	fprintf(stderr, "usage:  %s [-u user] [-l | -r | <file>]\n", PROGNAME);
	fprintf(stderr, " <cronfile>  : new crontab\n");
	fprintf(stderr, " -l          : list crontab\n");
	fprintf(stderr, " -r          : remove crontab\n");
	exit(1);
}

static void ParseArgs(int argc,char * argv[], char *User, char *Filename)
{	char *tmp,uflag=0;
	struct passwd *pwent;

	*Filename = '\0';

	while(--argc>0)			/* parse options */
	{	if(*argv[1]=='-')
		{	switch(*(++argv[1]))
			{ 
			case 'u': 
				if(argv[2] == NULL)
					usage();
				strcpy(User, argv[2]);
				uflag=1;	
				argv++;	argc--;
				break;
			case 'l':
				Option = list;
				break; 
			case 'r':
				Option = delete;
				break;
			default:  usage();
				 break;
			}
		}
		else
		{	Option = replace;
			strcpy( Filename, ux2dos(argv[1]));
		}
		argv++;
	}	

	if(!uflag)
	{	*User = '\0';
		if((tmp=getenv(USERENV)) != NULL)
			strcpy(User,tmp);
		else if((pwent = getpwuid(Pgetuid())) != NULL)
			strcpy(User,pwent->pw_name);
	}
	if(getpwnam(User) == NULL)
	{	fprintf(stderr,PROGNAME " : User unknown.\n");
		exit(1);
	}
}

static void ListCmd(char *User)
{	extern	errno;
	char	tab[MAX_FNAME];
	FILE	*f;
	int	ch;

	sprintf(tab, "%s/%s",CRONDIR,User);
	if ((f = fopen(ux2dos(tab), "r")) == NULL)
	{	if (errno == ENOENT)
			fprintf(stderr, "no crontab for %s\n", User);
		else
			perror(tab);
		exit(1);
	}

	while ((ch = fgetc(f)) != EOF)
		putchar(ch);
	fclose(f);
}

static void DeleteCmd(char *User)
{	extern	errno;
	char	tab[MAX_FNAME];

	sprintf(tab, "%s/%s",CRONDIR,User);
	if (remove(ux2dos(tab)))
	{	if (errno == ENOENT)
			fprintf(stderr, "no crontab for %s\n", User);
		else
			perror(tab);
		exit(1);
	}
	LogMsg(PROGNAME, getpid(), "crontab for %s deleted", User);
	printf("Crontab for %s removed\n", User);

	PokeDaemon(EXTERNBUILD);
}

static int CheckCrontab(FILE *NewCrontab)
{	char *ptr,jobline[MAX_TEMPSTR];
	int x,linenr=1;
	
	while(fgets(jobline,MAX_TEMPSTR -1,NewCrontab),!feof(NewCrontab))
	{	ptr = jobline;
		for(x=0;x<6;x++)	/* must have exactly 6 fields */
		{	while (*ptr=='\t' || *ptr==' ')
				ptr++;

			if (*ptr=='\n' || *ptr=='\0')
				return linenr;
			/* premature end or empty command */

			if(x<5 && isdigit(*ptr)==0 && *ptr!='*')
				return linenr;
			/* first 5 must start with a number or * sign */

			if(x==5)
			{	if(isdigit(*ptr))
					return linenr;
				/* command shouldn't start with a number */
				
				if(strlen(ptr) >= MAX_COMMAND)
					printf("Warning: command %d too long !\n",linenr);
			}

			while (*ptr!='\t' && *ptr!=' ' &&
				   *ptr!='\n' && *ptr!='\0') ptr++;
		}
		linenr++;
	}
	rewind(NewCrontab);
	return 0;
}

static void ReplaceCmd(char *User,char *Filename)
{	extern	errno;
	char	tab[MAX_FNAME], ntab[MAX_FNAME];
	FILE	*NewCrontab;
	FILE	*tmp;
	int		linenr,ch;

	if ((NewCrontab = fopen(ux2dos(Filename), "r")) == NULL)
	{	perror(Filename);
		exit(1);
	}

	if ((linenr=CheckCrontab(NewCrontab)) > 0)
	{	fprintf(stderr,PROGNAME
			" : Invalid crontab entry on line %d !\n",linenr);
		fclose(NewCrontab);
		exit(1);
	}
	
	sprintf(tab, "tmp.%d", getpid());
	sprintf(ntab, "%s/%s",CRONDIR,tab);
	if ((tmp = fopen(ux2dos(ntab), "w")) == NULL)
	{	perror(ntab);
		exit(1);
	}

	/* copy the crontab to the tmp	 */

	while (EOF != (ch = fgetc(NewCrontab)))
		fputc(ch, tmp);
	fclose(NewCrontab);
	fflush(tmp);  rewind(tmp);

	if (ferror(tmp))
	{	fprintf(stderr,PROGNAME " : error writing new crontab to %s\n",
			ntab);
		fclose(tmp);  remove(ntab);
		exit(1);
	}

	if (fclose(tmp) == EOF)
	{	perror("fclose");
		remove(ntab);
		exit(1);
	}

	sprintf(tab, "%s/%s", CRONDIR, User);
	remove(ux2dos(tab));
	if (rename(ntab, tab))
	{	fprintf(stderr,PROGNAME " : error renaming %s to %s\n",ntab, tab);
		perror("rename");
		remove(ntab);
		exit(1);
	}
	LogMsg(PROGNAME, getpid(), "crontab for %s replaced", User);

	PokeDaemon(EXTERNBUILD);
}

void main(int argc,char * argv[])
{	static char User[MAX_UNAME];
	static char Filename[MAX_FNAME];

	ParseArgs(argc, argv, User, Filename);
	
	switch (Option)
	{
		case list   :	ListCmd(User);		break;
		case delete :	DeleteCmd(User);	break;
		case replace:	ReplaceCmd(User,Filename);	break;
		case unknown:	usage();		break;
	}
	exit(0);
}
