~vcs-imports/anacron/main

1 by shaleh
Initial revision
1
/*
2
    Anacron - run commands periodically
3
    Copyright (C) 1998  Itai Tzur <itzur@actcom.co.il>
4
    Copyright (C) 1999  Sean 'Shaleh' Perry <shaleh@debian.org>
5
 
6
    This program is free software; you can redistribute it and/or modify
7
    it under the terms of the GNU General Public License as published by
8
    the Free Software Foundation; either version 2 of the License, or
9
    (at your option) any later version.
10
 
11
    This program is distributed in the hope that it will be useful,
12
    but WITHOUT ANY WARRANTY; without even the implied warranty of
13
    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
14
    GNU General Public License for more details.
15
 
16
    You should have received a copy of the GNU General Public License
17
    along with this program; if not, write to the Free Software
18
    Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
19
 
20
    The GNU General Public License can also be found in the file
21
    `COPYING' that comes with the Anacron source distribution.
22
*/
23
24
25
#include <errno.h>
26
#include <unistd.h>
27
#include <stdlib.h>
28
#include <sys/stat.h>
29
#include <pwd.h>
30
#include <sys/types.h>
31
#include <sys/wait.h>
32
#include <fcntl.h>
33
#include <signal.h>
34
#include <stdio.h>
35
#include <string.h>
36
#include "global.h"
37
38
static int
39
temp_file()
40
/* Open a temporary file and return its file descriptor */
41
{
42
    const int max_retries = 50;
43
    char *name;
44
    int fd, i;
45
46
    i = 0;
47
    name = NULL;
48
    do
49
    {
50
	i++;
51
	free(name);
52
	name = tempnam(NULL, NULL);
53
	if (name == NULL) die("Can't find a unique temporary filename");
54
	fd = open(name, O_RDWR | O_CREAT | O_EXCL | O_APPEND,
55
		  S_IRUSR | S_IWUSR);
56
	/* I'm not sure we actually need to be so persistent here */
57
    } while (fd == -1 && errno == EEXIST && i < max_retries);
58
    
59
    if (fd == -1) die_e("Can't open temporary file");
60
    if (unlink(name)) die_e("Can't unlink temporary file");
61
    free(name);
62
    fcntl(fd, F_SETFD, 1);    /* set close-on-exec flag */
63
    return fd;
64
}
65
66
static off_t
67
file_size(int fd)
68
/* Return the size of temporary file fd */
69
{
70
    struct stat st;
71
72
    if (fstat(fd, &st)) die_e("Can't fstat temporary file");
73
    return st.st_size;
74
}
75
76
static char *
77
username()
78
{
79
    struct passwd *ps;
80
81
    ps = getpwuid(geteuid());
82
    if (ps == NULL) die_e("getpwuid() error");
83
    return ps->pw_name;
84
}
85
86
static void
87
xputenv(const char *s)
88
{
89
    if (putenv(s)) die_e("Can't set the environment");
90
}
91
92
static void
93
setup_env(const job_rec *jr)
94
/* Setup the environment for the job according to /etc/anacrontab */
95
{
96
    env_rec *er;
97
98
    er = first_env_rec;
99
    if (er == NULL || jr->prev_env_rec == NULL) return;
100
    xputenv(er->assign);
101
    while (er != jr->prev_env_rec)
102
    {
103
	er = er->next;
104
	xputenv(er->assign);
105
    }
106
}
107
108
static void
109
run_job(const job_rec *jr)
110
/* This is called to start the job, after the fork */
111
{
112
    setup_env(jr);
113
    /* setup stdout and stderr */
114
    xclose(1);
115
    xclose(2);
116
    if (dup2(jr->output_fd, 1) != 1 || dup2(jr->output_fd, 2) != 2)
117
	die_e("dup2() error");     /* dup2 also clears close-on-exec flag */
118
    in_background = 0;  /* now, errors will be mailed to the user */
119
    if (chdir("/")) die_e("Can't chdir to '/'");
120
121
    umask(old_umask);
122
    if (sigprocmask(SIG_SETMASK, &old_sigmask, NULL))
123
	die_e("sigprocmask error");
124
    xcloselog();
125
    execl("/bin/sh", "/bin/sh", "-c", jr->command, (char *)NULL);
126
    die_e("execl() error");
127
}
128
129
static void
130
xwrite(int fd, const char *string)
131
/* Write (using write()) the string "string" to temporary file "fd".
132
 * Don't return on failure */
133
{
134
    if (write(fd, string, strlen(string)) == -1)
135
	die_e("Can't write to temporary file");
136
}
137
138
static int
139
xwait(pid_t pid , int *status)
140
/* Check if child process "pid" has finished.  If it has, return 1 and its
141
 * exit status in "*status".  If not, return 0.
142
 */
143
{
144
    pid_t r;
145
146
    r = waitpid(pid, status, WNOHANG);
147
    if (r == -1) die_e("waitpid() error");
148
    if (r == 0) return 0;
149
    return 1;
150
}
151
152
static void
153
launch_mailer(job_rec *jr)
154
{
155
    pid_t pid;
156
157
    pid = xfork();
158
    if (pid == 0)
159
    {
160
	/* child */
161
	in_background = 1;
162
	/* set stdin to the job's output */
163
	xclose(0);
164
	if (dup2(jr->output_fd, 0) != 0) die_e("Can't dup2()");
165
	if (lseek(0, 0, SEEK_SET) != 0) die_e("Can't lseek()");
166
	umask(old_umask);
167
	if (sigprocmask(SIG_SETMASK, &old_sigmask, NULL))
168
	    die_e("sigprocmask error");
169
	xcloselog();
170
171
	/* Here, I basically mirrored the way /usr/sbin/sendmail is called
172
	 * by cron on a Debian system, except for the "-oem" and "-or0s"
173
	 * options, which don't seem to be appropriate here.
174
	 * Hopefully, this will keep all the MTAs happy. */
175
	execl(SENDMAIL, SENDMAIL, "-FAnacron", "-odi",
176
	      username(), (char *)NULL);
177
	die_e("Can't exec " SENDMAIL);
178
    }
179
    /* parent */
180
    /* record mailer pid */
181
    jr->mailer_pid = pid;
182
    running_mailers++;
183
}
184
185
static void
186
tend_mailer(job_rec *jr, int status)
187
{
188
    if (WIFEXITED(status) && WEXITSTATUS(status) != 0)
189
	complain("Tried to mail output of job `%s', "
190
		 "but mailer process (" SENDMAIL ") exited with ststus %d",
191
		 jr->ident, WEXITSTATUS(status));
192
    else if (!WIFEXITED(status) && WIFSIGNALED(status))
193
	complain("Tried to mail output of job `%s', "
194
		 "but mailer process (" SENDMAIL ") got signal %d",
195
		 jr->ident, WTERMSIG(status));
196
    else if (!WIFEXITED(status) && !WIFSIGNALED(status))
197
	complain("Tried to mail output of job `%s', "
198
		 "but mailer process (" SENDMAIL ") terminated abnormally"
199
		 , jr->ident);
200
201
    jr->mailer_pid = 0;
202
    running_mailers--;
203
}
204
205
void
206
launch_job(job_rec *jr)
207
{
208
    pid_t pid;
209
    int fd;
210
211
    /* create temporary file for stdout and stderr of the job */
212
    fd = jr->output_fd = temp_file();
213
    /* write mail header */
214
    xwrite(fd, "From: ");
215
    xwrite(fd, username());
216
    xwrite(fd, " (Anacron)\n");
217
    xwrite(fd, "To: ");
218
    xwrite(fd, username());
219
    xwrite(fd, "\n");
220
    xwrite(fd, "Subject: Anacron job '");
221
    xwrite(fd, jr->ident);
222
    xwrite(fd, "'\n\n");
223
    jr->mail_header_size = file_size(fd);
224
225
    pid = xfork();
226
    if (pid == 0)
227
    {
228
	/* child */
229
	in_background = 1;
230
	run_job(jr);
231
	/* execution never gets here */
232
    }
233
    /* parent */
234
    explain("Job `%s' started", jr->ident);
235
    jr->job_pid = pid;
236
    running_jobs++;
237
}
238
239
static void
240
tend_job(job_rec *jr, int status)
241
/* Take care of a finished job */
242
{
243
    int mail_output;
244
    char *m;
245
246
    update_timestamp(jr);
247
    unlock(jr);
248
    if (file_size(jr->output_fd) > jr->mail_header_size) mail_output = 1;
249
    else mail_output = 0;
250
251
    m = mail_output ? " (mailing output)" : "";
252
    if (WIFEXITED(status) && WEXITSTATUS(status) == 0)
253
	explain("Job `%s' terminated%s", jr->ident, m);
254
    else if (WIFEXITED(status))
255
	explain("Job `%s' terminated (exit status: %d)%s",
256
		jr->ident, WEXITSTATUS(status), m);
257
    else if (WIFSIGNALED(status))
258
	complain("Job `%s' terminated due to signal %d%s",
259
		 jr->ident, WTERMSIG(status), m);
260
    else /* is this possible? */
261
	complain("Job `%s' terminated abnormally%s", jr->ident, m);
262
263
    jr->job_pid = 0;
264
    running_jobs--;
265
    if (mail_output) launch_mailer(jr);
266
    xclose(jr->output_fd);
267
}
268
269
void
270
tend_children()
271
/* This is called whenever we get a SIGCHLD.
272
 * Takes care of zombie children.
273
 */
274
{
275
    int j;
276
    int status;
277
278
    j = 0;
279
    while (j < njobs)
280
    {
281
	if (job_array[j]->mailer_pid != 0 &&
282
	    xwait(job_array[j]->mailer_pid, &status))
283
	    tend_mailer(job_array[j], status);
284
	if (job_array[j]->job_pid != 0 &&
285
	    xwait(job_array[j]->job_pid, &status))
286
	    tend_job(job_array[j], status);
287
	j++;
288
    }
289
}