[comp.sources.misc] PD Cron 03/03

paul@vixie.UUCP (Paul Vixie Esq) (06/12/87)

#! /bin/sh
##  This is a shell archive.  Remove anything before this line, then unpack
##  it by saving it into a file and typing "sh file".  To overwrite existing
##  files, type "sh file -c".  You can also feed this as standard input via
##  unshar, or by typing "sh <file".  If this archive is complete, you will
##  see the following message at the end:
#		"End of archive 3 (of 3)."
# Contents:  do_command.c
# Wrapped by paul@vixie on Wed May  6 10:16:04 1987
PATH=/bin:/usr/bin:/usr/ucb ; export PATH
if test -f do_command.c -a "${1}" != "-c" ; then 
  echo shar: Will not over-write existing file \"do_command.c\"
else
echo shar: Extracting \"do_command.c\" \(9944 characters\)
sed "s/^X//" >do_command.c <<'END_OF_do_command.c'
X#if !defined(lint) && !defined(LINT)
Xstatic char rcsid[] = "$Header: do_command.c,v 1.4 87/05/02 17:33:35 paul Exp $";
X#endif
X
X/* $Source: /usr/src/local/vix/cron/do_command.c,v $
X * $Revision: 1.4 $
X * $Log:	do_command.c,v $
X * Revision 1.4  87/05/02  17:33:35  paul
X * baseline for mod.sources release
X * 
X * Revision 1.3  87/04/09  00:03:58  paul
X * improved data hiding, locality of declaration/references
X * fixed a rs@mirror bug by redesigning the mailto stuff completely
X * 
X * Revision 1.2  87/03/19  12:46:24  paul
X * implemented suggestions from rs@mirror (Rich $alz):
X *    MAILTO="" means no mail should be sent
X *    various fixes of bugs or lint complaints
X *    put a To: line in the mail message
X * 
X * Revision 1.1  87/01/26  23:47:00  paul
X * Initial revision
X */
X
X/* Copyright 1987 by Vixie Enterprises
X * All rights reserved
X *
X * Distribute freely, except: don't sell it, don't remove my name from the
X * source or documentation (don't take credit for my work), mark your changes
X * (don't get me blamed for your possible bugs), don't alter or remove this
X * notice.  Commercial redistribution is negotiable; contact me for details.
X *
X * Send bug reports, bug fixes, enhancements, requests, flames, etc., and
X * I'll try to keep a version up to date.  I can be reached as follows:
X * Paul Vixie, Vixie Enterprises, 329 Noe Street, San Francisco, CA, 94114,
X * (415) 864-7013, {ucbvax!dual,ames,ucsfmis,lll-crg,sun}!ptsfa!vixie!paul.
X */
X
X
X#include "cron.h"
X#include <signal.h>
X#include <pwd.h>
X#if defined(BSD)
X# include <syslog.h>
X# include <sys/wait.h>
X#endif /*BSD*/
X
Xvoid
Xdo_command(cmd, u)
X	char	*cmd;
X	user	*u;
X{
X	extern int	fork(), _exit();
X	extern char	*env_get();
X	extern void	child_process();
X
X	Debug(DPROC, ("do_command(%s, (%s,%d,%d))\n",
X		cmd, env_get(USERENV, u->envp), u->uid, u->gid))
X
X	/* fork to become asyncronous -- parent process is done immediately,
X	 * and continues to run the normal cron code, which means return to
X	 * tick().  the child and grandchild don't leave this function, alive.
X	 *
X	 * vfork() is unsuitable, since we have much to do, and the parent
X	 * needs to be able to run off and fork other processes.
X	 */
X	if (fork() == 0)
X	{
X		child_process(cmd, u);
X		Debug(DPROC, ("[%d] child process done, exiting\n", getpid()))
X		_exit(OK_EXIT);
X	}
X	Debug(DPROC, ("[%d] main process returning to work\n", getpid()))
X}
X
X
Xvoid
Xchild_process(cmd, u)
X	char	*cmd;
X	user	*u;
X{
X	extern struct passwd	*getpwnam();
X	extern void	sigpipe_func(), be_different();
X	extern int	VFORK();
X	extern char	*index(), *env_get();
X
X	auto int	stdin_pipe[2], stdout_pipe[2];
X	register char	*input_data;
X
X
X	Debug(DPROC, ("[%d] child process running\n", getpid()))
X
X	/* mark ourselves as different to PS command watchers by upshifting
X	 * our program name.
X	 */
X	{
X		register char	*pch;
X
X		for (pch = PROGNAME;  *pch;  pch++)
X			*pch = MkUpper(*pch);
X	}
X
X#if defined(BSD)
X	/* our parent is watching for our death by catching SIGCHLD.  we
X	 * do not care to watch for our children's deaths this way -- we
X	 * use wait() explictly.  so we have to disable the signal (which
X	 * was inherited from the parent.
X	 *
X	 * this isn't needed for system V, since our parent is already
X	 * SIG_IGN on SIGCLD -- which, hopefully, will cause children to
X	 * simply vanish when then die.
X	 */
X	(void) signal(SIGCHLD, SIG_IGN);
X#endif /*BSD*/
X
X	/* create some pipes to talk to our future child
X	 */
X	pipe(stdin_pipe);	/* child's stdin */
X	pipe(stdout_pipe);	/* child's stdout */
X	
X	/* since we are a forked process, we can diddle the command string
X	 * we were passed -- nobody else is going to use it again, right?
X	 *
X	 * if a % is present in the command, previous characters are the
X	 * command, and subsequent characters are the additional input to
X	 * the command.  Subsequent %'s will be transformed into newlines,
X	 * but that happens later.
X	 */
X	if (NULL == (input_data = index(cmd, '%')))
X	{
X		/* no %.  point input_data at a null string.
X		 */
X		input_data = "";
X	}
X	else
X	{
X		/* % found.  replace with a null (remember, we're a forked
X		 * process and the string won't be reused), and increment
X		 * input_data to point at the following character.
X		 */
X		*input_data++ = '\0';
X	}
X
X	/* fork again, this time so we can exec the users' command.
X	 */
X	if (VFORK() == 0)
X	{
X		Debug(DPROC, ("[%d] grandchild process VFORK()'ed\n", getpid()))
X
X		/* get new pgrp, void tty, etc.
X		 */
X		be_different();
X
X		/* close the pipe ends that we won't use.  this doesn't affect
X		 * the parent, who has to read and write them; it keeps the
X		 * kernel from recording us as a potential client TWICE --
X		 * which would keep it from sending SIGPIPE in otherwise
X		 * appropriate circumstances.
X		 */
X		close(stdin_pipe[WRITE_PIPE]);
X		close(stdout_pipe[READ_PIPE]);
X
X		/* grandchild process.  make std{in,out} be the ends of
X		 * pipes opened by our daddy; make stderr go to stdout.
X		 */
X		close(STDIN);	dup2(stdin_pipe[READ_PIPE], STDIN);
X		close(STDOUT);	dup2(stdout_pipe[WRITE_PIPE], STDOUT);
X		close(STDERR);	dup2(STDOUT, STDERR);
X
X		/* close the pipes we just dup'ed.  The resources will remain,
X		 * since they've been dup'ed... :-)...
X		 */
X		close(stdin_pipe[READ_PIPE]);
X		close(stdout_pipe[WRITE_PIPE]);
X
X		/* set our directory, uid and gid.
X		 */
X		setgid(u->gid);		/* set group first! */
X		initgroups(env_get(USERENV, u->envp), u->gid);
X		setuid(u->uid);		/* you aren't root after this... */
X		chdir(env_get("HOME", u->envp));
X
X		/* exec the command.
X		 */
X		{
X			char	*shell = env_get("SHELL", u->envp);
X
X			execle(shell, shell, "-c", cmd, (char *)0, u->envp);
X			fprintf(stderr, "execl: couldn't exec `%s'\n", shell);
X			perror("execl");
X			_exit(ERROR_EXIT);
X		}
X	}
X
X	/* middle process, child of original cron, parent of process running
X	 * the user's command.
X	 */
X
X	Debug(DPROC, ("[%d] child continues, closing pipes\n", getpid()))
X
X	/* close the ends of the pipe that will only be referenced in the
X	 * son process...
X	 */
X	close(stdin_pipe[READ_PIPE]);
X	close(stdout_pipe[WRITE_PIPE]);
X
X	/*
X	 * write, to the pipe connected to child's stdin, any input specified
X	 * after a % in the crontab entry; we will assume that it will all
X	 * fit, which means (on BSD anyway) that it should be 4096 bytes or
X	 * less.  this seems reasonable.  while we copy, convert any
X	 * additional %'s to newlines.  when done, if some characters were
X	 * written and the last one wasn't a newline, write a newline.
X	 */
X
X	Debug(DPROC, ("[%d] child sending data to grandchild\n", getpid()))
X
X	{
X		register FILE	*out = fdopen(stdin_pipe[WRITE_PIPE], "w");
X		register int	need_newline = FALSE;
X		register int	escaped = FALSE;
X
X		while (*input_data)
X		{
X			if (!escaped && *input_data == '%')
X			{
X				need_newline = FALSE;
X				putc('\n', out);
X			}
X			else
X			{
X				need_newline = TRUE;
X				putc(*input_data, out);
X				escaped = (*input_data == '\\');
X			}
X			input_data++;
X		}
X		if (need_newline)
X			putc('\n', out);
X
X		/* write 0-length message; this is EOF in a pipe (I hope)
X		 */
X		fflush(out);
X		write(stdin_pipe[WRITE_PIPE], "", 0);
X		fclose(out);
X		close(stdin_pipe[WRITE_PIPE]);
X
X		Debug(DPROC, ("[%d] child done sending to grandchild\n", getpid()))
X	}
X
X	/*
X	 * read output from the grandchild.  it's stderr has been redirected to
X	 * it's stdout, which has been redirected to our pipe.  if there is any
X	 * output, we'll be mailing it to the user whose crontab this is...
X	 * when the grandchild exits, we'll get EOF.
X	 */
X
X	Debug(DPROC, ("[%d] child reading output from grandchild\n", getpid()))
X
X	{
X		register FILE	*in = fdopen(stdout_pipe[READ_PIPE], "r");
X		register int	ch;
X
X		if (EOF != (ch = getc(in)))
X		{
X			FILE	*mail;
X			char	*usernm, *mailto;
X
X			Debug(DPROC, ("[%d] got data (%x:%c) from grandchild\n",
X				getpid(), ch, ch))
X
X			/* get name of recipient.  this is MAILTO if set to a
X			 * valid local username; USER otherwise.
X			 */
X			usernm = env_get(USERENV, u->envp);
X			mailto = env_get("MAILTO", u->envp);
X			if (mailto)
X			{
X				/* MAILTO was present in the environment
X				 */
X				if (!*mailto)
X				{
X					/* ... but it's empty. set to NULL
X					 */
X					mailto = NULL;
X				}
X				else
X				{
X					/* not empty -- verify it,
X					 * setting to USER if not valid.
X					 */
X					if (NULL == getpwnam(mailto))
X						mailto = usernm;
X					endpwent();
X				}
X			}
X			else
X			{
X				/* MAILTO not present, set to USER.
X				 */
X				mailto = usernm;
X			}
X		
X			/* if we are supposed to be mailing, MAILTO will
X			 * be non-NULL.  only in this case should we set
X			 * up the mail command and subjects and stuff...
X			 */
X
X			if (mailto)
X			{
X				extern FILE	*popen();
X				extern char	*sprintf();
X				register char	**env;
X				auto char	mailcmd[MAX_COMMAND];
X
X				(void) sprintf(mailcmd, MAILCMD, mailto);
X				if (!(mail = popen(mailcmd, "w")))
X				{
X					perror(MAILCMD);
X					(void) _exit(ERROR_EXIT);
X				}
X				fprintf(mail, "To: %s\n", mailto);
X				fprintf(mail, "Subject: %s\n", MAILSUBJ);
X				fprintf(mail, "X-cron-cmd: <%s>\n", cmd);
X				for (env = u->envp;  *env;  env++)
X					fprintf(mail, "X-cron-env: <%s>\n",
X						*env);
X				fprintf(mail, "\n");
X
X				/* this was the first char from the pipe
X				 */
X				putc(ch, mail);
X			}
X
X			/* we have to read the input pipe no matter whether
X			 * we mail or not, but obviously we only write to
X			 * mail pipe if we ARE mailing.
X			 */
X
X			while (EOF != (ch = getc(in)))
X			{
X				if (mailto)
X					putc(ch, mail);
X			}
X
X			/* only close pipe if we opened it -- i.e., we're
X			 * mailing...
X			 */
X
X			if (mailto)
X				pclose(mail);
X
X		} /*if data from grandchild*/
X
X		Debug(DPROC, ("[%d] got EOF from grandchild\n", getpid()))
X
X		fclose(in);
X		close(stdout_pipe[READ_PIPE]);
X	}
X
X#if defined(BSD)
X	/* wait for child to die.
X	 */
X	{
X		int		pid;
X		union wait	waiter;
X
X		Debug(DPROC, ("[%d] waiting for grandchild to finish\n", getpid()))
X		pid = wait(&waiter);
X		Debug(DPROC, ("[%d] grandchild #%d finished, status=%d\n",
X			getpid(), pid, waiter.w_status))
X	}
X#endif /*BSD*/
X}
END_OF_do_command.c
if test 9944 -ne `wc -c <do_command.c`; then
    echo shar: \"do_command.c\" unpacked with wrong size!
fi
# end of overwriting check
fi
echo shar: End of archive 3 \(of 3\).
cp /dev/null ark3isdone
MISSING=""
for I in 1 2 3 ; do
    if test ! -f ark${I}isdone ; then
	MISSING="${MISSING} ${I}"
    fi
done
if test "${MISSING}" = "" ; then
    echo You have unpacked all 3 archives.
    rm -f ark[1-9]isdone
else
    echo You still need to unpack the following archives:
    echo "        " ${MISSING}
fi
##  End of shell archive.
exit 0