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 |
}
|