diff options
Diffstat (limited to 'miscutils/crond.c')
-rw-r--r-- | miscutils/crond.c | 893 |
1 files changed, 445 insertions, 448 deletions
diff --git a/miscutils/crond.c b/miscutils/crond.c index f51159233..66110bb85 100644 --- a/miscutils/crond.c +++ b/miscutils/crond.c | |||
@@ -17,56 +17,55 @@ | |||
17 | /* glibc frees previous setenv'ed value when we do next setenv() | 17 | /* glibc frees previous setenv'ed value when we do next setenv() |
18 | * of the same variable. uclibc does not do this! */ | 18 | * of the same variable. uclibc does not do this! */ |
19 | #if (defined(__GLIBC__) && !defined(__UCLIBC__)) /* || OTHER_SAFE_LIBC... */ | 19 | #if (defined(__GLIBC__) && !defined(__UCLIBC__)) /* || OTHER_SAFE_LIBC... */ |
20 | #define SETENV_LEAKS 0 | 20 | # define SETENV_LEAKS 0 |
21 | #else | 21 | #else |
22 | #define SETENV_LEAKS 1 | 22 | # define SETENV_LEAKS 1 |
23 | #endif | 23 | #endif |
24 | 24 | ||
25 | 25 | ||
26 | #define TMPDIR CONFIG_FEATURE_CROND_DIR | 26 | #define TMPDIR CONFIG_FEATURE_CROND_DIR |
27 | #define CRONTABS CONFIG_FEATURE_CROND_DIR "/crontabs" | 27 | #define CRONTABS CONFIG_FEATURE_CROND_DIR "/crontabs" |
28 | #ifndef SENDMAIL | 28 | #ifndef SENDMAIL |
29 | #define SENDMAIL "sendmail" | 29 | # define SENDMAIL "sendmail" |
30 | #endif | 30 | #endif |
31 | #ifndef SENDMAIL_ARGS | 31 | #ifndef SENDMAIL_ARGS |
32 | #define SENDMAIL_ARGS "-ti", NULL | 32 | # define SENDMAIL_ARGS "-ti" |
33 | #endif | 33 | #endif |
34 | #ifndef CRONUPDATE | 34 | #ifndef CRONUPDATE |
35 | #define CRONUPDATE "cron.update" | 35 | # define CRONUPDATE "cron.update" |
36 | #endif | 36 | #endif |
37 | #ifndef MAXLINES | 37 | #ifndef MAXLINES |
38 | #define MAXLINES 256 /* max lines in non-root crontabs */ | 38 | # define MAXLINES 256 /* max lines in non-root crontabs */ |
39 | #endif | 39 | #endif |
40 | 40 | ||
41 | 41 | ||
42 | typedef struct CronFile { | 42 | typedef struct CronFile { |
43 | struct CronFile *cf_Next; | 43 | struct CronFile *cf_next; |
44 | struct CronLine *cf_LineBase; | 44 | struct CronLine *cf_lines; |
45 | char *cf_User; /* username */ | 45 | char *cf_username; |
46 | smallint cf_Ready; /* bool: one or more jobs ready */ | 46 | smallint cf_wants_starting; /* bool: one or more jobs ready */ |
47 | smallint cf_Running; /* bool: one or more jobs running */ | 47 | smallint cf_has_running; /* bool: one or more jobs running */ |
48 | smallint cf_Deleted; /* marked for deletion, ignore */ | 48 | smallint cf_deleted; /* marked for deletion (but still has running jobs) */ |
49 | } CronFile; | 49 | } CronFile; |
50 | 50 | ||
51 | typedef struct CronLine { | 51 | typedef struct CronLine { |
52 | struct CronLine *cl_Next; | 52 | struct CronLine *cl_next; |
53 | char *cl_Shell; /* shell command */ | 53 | char *cl_cmd; /* shell command */ |
54 | pid_t cl_Pid; /* running pid, 0, or armed (-1) */ | 54 | pid_t cl_pid; /* >0:running, <0:needs to be started in this minute, 0:dormant */ |
55 | #if ENABLE_FEATURE_CROND_CALL_SENDMAIL | 55 | #if ENABLE_FEATURE_CROND_CALL_SENDMAIL |
56 | int cl_MailPos; /* 'empty file' size */ | 56 | int cl_empty_mail_size; /* size of mail header only, 0 if no mailfile */ |
57 | smallint cl_MailFlag; /* running pid is for mail */ | 57 | char *cl_mailto; /* whom to mail results, may be NULL */ |
58 | char *cl_MailTo; /* whom to mail results */ | ||
59 | #endif | 58 | #endif |
60 | /* ordered by size, not in natural order. makes code smaller: */ | 59 | /* ordered by size, not in natural order. makes code smaller: */ |
61 | char cl_Dow[7]; /* 0-6, beginning sunday */ | 60 | char cl_Dow[7]; /* 0-6, beginning sunday */ |
62 | char cl_Mons[12]; /* 0-11 */ | 61 | char cl_Mons[12]; /* 0-11 */ |
63 | char cl_Hrs[24]; /* 0-23 */ | 62 | char cl_Hrs[24]; /* 0-23 */ |
64 | char cl_Days[32]; /* 1-31 */ | 63 | char cl_Days[32]; /* 1-31 */ |
65 | char cl_Mins[60]; /* 0-59 */ | 64 | char cl_Mins[60]; /* 0-59 */ |
66 | } CronLine; | 65 | } CronLine; |
67 | 66 | ||
68 | 67 | ||
69 | #define DaemonUid 0 | 68 | #define DAEMON_UID 0 |
70 | 69 | ||
71 | 70 | ||
72 | enum { | 71 | enum { |
@@ -79,49 +78,30 @@ enum { | |||
79 | OPT_d = (1 << 6) * ENABLE_FEATURE_CROND_D, | 78 | OPT_d = (1 << 6) * ENABLE_FEATURE_CROND_D, |
80 | }; | 79 | }; |
81 | #if ENABLE_FEATURE_CROND_D | 80 | #if ENABLE_FEATURE_CROND_D |
82 | #define DebugOpt (option_mask32 & OPT_d) | 81 | # define DebugOpt (option_mask32 & OPT_d) |
83 | #else | 82 | #else |
84 | #define DebugOpt 0 | 83 | # define DebugOpt 0 |
85 | #endif | 84 | #endif |
86 | 85 | ||
87 | 86 | ||
88 | struct globals { | 87 | struct globals { |
89 | unsigned LogLevel; /* = 8; */ | 88 | unsigned log_level; /* = 8; */ |
90 | const char *LogFile; | 89 | time_t crontab_dir_mtime; |
91 | const char *CDir; /* = CRONTABS; */ | 90 | const char *log_filename; |
92 | CronFile *FileBase; | 91 | const char *crontab_dir_name; /* = CRONTABS; */ |
92 | CronFile *cron_files; | ||
93 | #if SETENV_LEAKS | 93 | #if SETENV_LEAKS |
94 | char *env_var_user; | 94 | char *env_var_user; |
95 | char *env_var_home; | 95 | char *env_var_home; |
96 | #endif | 96 | #endif |
97 | } FIX_ALIASING; | 97 | } FIX_ALIASING; |
98 | #define G (*(struct globals*)&bb_common_bufsiz1) | 98 | #define G (*(struct globals*)&bb_common_bufsiz1) |
99 | #define LogLevel (G.LogLevel ) | ||
100 | #define LogFile (G.LogFile ) | ||
101 | #define CDir (G.CDir ) | ||
102 | #define FileBase (G.FileBase ) | ||
103 | #define env_var_user (G.env_var_user ) | ||
104 | #define env_var_home (G.env_var_home ) | ||
105 | #define INIT_G() do { \ | 99 | #define INIT_G() do { \ |
106 | LogLevel = 8; \ | 100 | G.log_level = 8; \ |
107 | CDir = CRONTABS; \ | 101 | G.crontab_dir_name = CRONTABS; \ |
108 | } while (0) | 102 | } while (0) |
109 | 103 | ||
110 | 104 | ||
111 | static void CheckUpdates(void); | ||
112 | static void SynchronizeDir(void); | ||
113 | static int TestJobs(time_t t1, time_t t2); | ||
114 | static void RunJobs(void); | ||
115 | static int CheckJobs(void); | ||
116 | static void RunJob(const char *user, CronLine *line); | ||
117 | #if ENABLE_FEATURE_CROND_CALL_SENDMAIL | ||
118 | static void EndJob(const char *user, CronLine *line); | ||
119 | #else | ||
120 | #define EndJob(user, line) ((line)->cl_Pid = 0) | ||
121 | #endif | ||
122 | static void DeleteFile(const char *userName); | ||
123 | |||
124 | |||
125 | /* 0 is the most verbose, default 8 */ | 105 | /* 0 is the most verbose, default 8 */ |
126 | #define LVL5 "\x05" | 106 | #define LVL5 "\x05" |
127 | #define LVL7 "\x07" | 107 | #define LVL7 "\x07" |
@@ -138,12 +118,12 @@ static void crondlog(const char *ctl, ...) | |||
138 | int level = (ctl[0] & 0x1f); | 118 | int level = (ctl[0] & 0x1f); |
139 | 119 | ||
140 | va_start(va, ctl); | 120 | va_start(va, ctl); |
141 | if (level >= (int)LogLevel) { | 121 | if (level >= (int)G.log_level) { |
142 | /* Debug mode: all to (non-redirected) stderr, */ | 122 | /* Debug mode: all to (non-redirected) stderr, */ |
143 | /* Syslog mode: all to syslog (logmode = LOGMODE_SYSLOG), */ | 123 | /* Syslog mode: all to syslog (logmode = LOGMODE_SYSLOG), */ |
144 | if (!DebugOpt && LogFile) { | 124 | if (!DebugOpt && G.log_filename) { |
145 | /* Otherwise (log to file): we reopen log file at every write: */ | 125 | /* Otherwise (log to file): we reopen log file at every write: */ |
146 | int logfd = open3_or_warn(LogFile, O_WRONLY | O_CREAT | O_APPEND, 0666); | 126 | int logfd = open3_or_warn(G.log_filename, O_WRONLY | O_CREAT | O_APPEND, 0666); |
147 | if (logfd >= 0) | 127 | if (logfd >= 0) |
148 | xmove_fd(logfd, STDERR_FILENO); | 128 | xmove_fd(logfd, STDERR_FILENO); |
149 | } | 129 | } |
@@ -163,142 +143,6 @@ static void crondlog(const char *ctl, ...) | |||
163 | exit(20); | 143 | exit(20); |
164 | } | 144 | } |
165 | 145 | ||
166 | int crond_main(int argc, char **argv) MAIN_EXTERNALLY_VISIBLE; | ||
167 | int crond_main(int argc UNUSED_PARAM, char **argv) | ||
168 | { | ||
169 | unsigned opts; | ||
170 | |||
171 | INIT_G(); | ||
172 | |||
173 | /* "-b after -f is ignored", and so on for every pair a-b */ | ||
174 | opt_complementary = "f-b:b-f:S-L:L-S" IF_FEATURE_CROND_D(":d-l") | ||
175 | ":l+:d+"; /* -l and -d have numeric param */ | ||
176 | opts = getopt32(argv, "l:L:fbSc:" IF_FEATURE_CROND_D("d:"), | ||
177 | &LogLevel, &LogFile, &CDir | ||
178 | IF_FEATURE_CROND_D(,&LogLevel)); | ||
179 | /* both -d N and -l N set the same variable: LogLevel */ | ||
180 | |||
181 | if (!(opts & OPT_f)) { | ||
182 | /* close stdin, stdout, stderr. | ||
183 | * close unused descriptors - don't need them. */ | ||
184 | bb_daemonize_or_rexec(DAEMON_CLOSE_EXTRA_FDS, argv); | ||
185 | } | ||
186 | |||
187 | if (!(opts & OPT_d) && LogFile == NULL) { | ||
188 | /* logging to syslog */ | ||
189 | openlog(applet_name, LOG_CONS | LOG_PID, LOG_CRON); | ||
190 | logmode = LOGMODE_SYSLOG; | ||
191 | } | ||
192 | |||
193 | xchdir(CDir); | ||
194 | //signal(SIGHUP, SIG_IGN); /* ? original crond dies on HUP... */ | ||
195 | xsetenv("SHELL", DEFAULT_SHELL); /* once, for all future children */ | ||
196 | crondlog(LVL8 "crond (busybox "BB_VER") started, log level %d", LogLevel); | ||
197 | SynchronizeDir(); | ||
198 | |||
199 | /* main loop - synchronize to 1 second after the minute, minimum sleep | ||
200 | * of 1 second. */ | ||
201 | { | ||
202 | time_t t1 = time(NULL); | ||
203 | int rescan = 60; | ||
204 | int sleep_time = 60; | ||
205 | |||
206 | write_pidfile("/var/run/crond.pid"); | ||
207 | for (;;) { | ||
208 | time_t t2; | ||
209 | long dt; | ||
210 | |||
211 | sleep((sleep_time + 1) - (time(NULL) % sleep_time)); | ||
212 | |||
213 | t2 = time(NULL); | ||
214 | dt = (long)t2 - (long)t1; | ||
215 | |||
216 | /* | ||
217 | * The file 'cron.update' is checked to determine new cron | ||
218 | * jobs. The directory is rescanned once an hour to deal | ||
219 | * with any screwups. | ||
220 | * | ||
221 | * check for disparity. Disparities over an hour either way | ||
222 | * result in resynchronization. A reverse-indexed disparity | ||
223 | * less then an hour causes us to effectively sleep until we | ||
224 | * match the original time (i.e. no re-execution of jobs that | ||
225 | * have just been run). A forward-indexed disparity less then | ||
226 | * an hour causes intermediate jobs to be run, but only once | ||
227 | * in the worst case. | ||
228 | * | ||
229 | * when running jobs, the inequality used is greater but not | ||
230 | * equal to t1, and less then or equal to t2. | ||
231 | */ | ||
232 | if (--rescan == 0) { | ||
233 | rescan = 60; | ||
234 | SynchronizeDir(); | ||
235 | } | ||
236 | CheckUpdates(); | ||
237 | if (DebugOpt) | ||
238 | crondlog(LVL5 "wakeup dt=%ld", dt); | ||
239 | if (dt < -60 * 60 || dt > 60 * 60) { | ||
240 | crondlog(WARN9 "time disparity of %ld minutes detected", dt / 60); | ||
241 | } else if (dt > 0) { | ||
242 | TestJobs(t1, t2); | ||
243 | RunJobs(); | ||
244 | sleep(5); | ||
245 | if (CheckJobs() > 0) { | ||
246 | sleep_time = 10; | ||
247 | } else { | ||
248 | sleep_time = 60; | ||
249 | } | ||
250 | } | ||
251 | t1 = t2; | ||
252 | } /* for (;;) */ | ||
253 | } | ||
254 | |||
255 | return 0; /* not reached */ | ||
256 | } | ||
257 | |||
258 | #if SETENV_LEAKS | ||
259 | /* We set environment *before* vfork (because we want to use vfork), | ||
260 | * so we cannot use setenv() - repeated calls to setenv() may leak memory! | ||
261 | * Using putenv(), and freeing memory after unsetenv() won't leak */ | ||
262 | static void safe_setenv(char **pvar_val, const char *var, const char *val) | ||
263 | { | ||
264 | char *var_val = *pvar_val; | ||
265 | |||
266 | if (var_val) { | ||
267 | bb_unsetenv(var_val); | ||
268 | free(var_val); | ||
269 | } | ||
270 | *pvar_val = xasprintf("%s=%s", var, val); | ||
271 | putenv(*pvar_val); | ||
272 | } | ||
273 | #endif | ||
274 | |||
275 | static void SetEnv(struct passwd *pas) | ||
276 | { | ||
277 | #if SETENV_LEAKS | ||
278 | safe_setenv(&env_var_user, "USER", pas->pw_name); | ||
279 | safe_setenv(&env_var_home, "HOME", pas->pw_dir); | ||
280 | /* if we want to set user's shell instead: */ | ||
281 | /*safe_setenv(env_var_user, "SHELL", pas->pw_shell);*/ | ||
282 | #else | ||
283 | xsetenv("USER", pas->pw_name); | ||
284 | xsetenv("HOME", pas->pw_dir); | ||
285 | #endif | ||
286 | /* currently, we use constant one: */ | ||
287 | /*setenv("SHELL", DEFAULT_SHELL, 1); - done earlier */ | ||
288 | } | ||
289 | |||
290 | static void ChangeUser(struct passwd *pas) | ||
291 | { | ||
292 | /* careful: we're after vfork! */ | ||
293 | change_identity(pas); /* - initgroups, setgid, setuid */ | ||
294 | if (chdir(pas->pw_dir) < 0) { | ||
295 | crondlog(WARN9 "chdir(%s)", pas->pw_dir); | ||
296 | if (chdir(TMPDIR) < 0) { | ||
297 | crondlog(DIE9 "chdir(%s)", TMPDIR); /* exits */ | ||
298 | } | ||
299 | } | ||
300 | } | ||
301 | |||
302 | static const char DowAry[] ALIGN1 = | 146 | static const char DowAry[] ALIGN1 = |
303 | "sun""mon""tue""wed""thu""fri""sat" | 147 | "sun""mon""tue""wed""thu""fri""sat" |
304 | /* "Sun""Mon""Tue""Wed""Thu""Fri""Sat" */ | 148 | /* "Sun""Mon""Tue""Wed""Thu""Fri""Sat" */ |
@@ -415,7 +259,7 @@ static void ParseField(char *user, char *ary, int modvalue, int off, | |||
415 | return; | 259 | return; |
416 | } | 260 | } |
417 | 261 | ||
418 | if (DebugOpt && (LogLevel <= 5)) { /* like LVL5 */ | 262 | if (DebugOpt && (G.log_level <= 5)) { /* like LVL5 */ |
419 | /* can't use crondlog, it inserts '\n' */ | 263 | /* can't use crondlog, it inserts '\n' */ |
420 | int i; | 264 | int i; |
421 | for (i = 0; i < modvalue; ++i) | 265 | for (i = 0; i < modvalue; ++i) |
@@ -450,7 +294,60 @@ static void FixDayDow(CronLine *line) | |||
450 | } | 294 | } |
451 | } | 295 | } |
452 | 296 | ||
453 | static void SynchronizeFile(const char *fileName) | 297 | /* |
298 | * delete_cronfile() - delete user database | ||
299 | * | ||
300 | * Note: multiple entries for same user may exist if we were unable to | ||
301 | * completely delete a database due to running processes. | ||
302 | */ | ||
303 | //FIXME: we will start a new job even if the old job is running | ||
304 | //if crontab was reloaded: crond thinks that "new" job is different from "old" | ||
305 | //even if they are in fact completely the same. Example | ||
306 | //Crontab was: | ||
307 | // 0-59 * * * * job1 | ||
308 | // 0-59 * * * * long_running_job2 | ||
309 | //User edits crontab to: | ||
310 | // 0-59 * * * * job1_updated | ||
311 | // 0-59 * * * * long_running_job2 | ||
312 | //Bug: crond can now start another long_running_job2 even if old one | ||
313 | //is still running. | ||
314 | //OTOH most other versions of cron do not wait for job termination anyway, | ||
315 | //they end up with multiple copies of jobs if they don't terminate soon enough. | ||
316 | static void delete_cronfile(const char *userName) | ||
317 | { | ||
318 | CronFile **pfile = &G.cron_files; | ||
319 | CronFile *file; | ||
320 | |||
321 | while ((file = *pfile) != NULL) { | ||
322 | if (strcmp(userName, file->cf_username) == 0) { | ||
323 | CronLine **pline = &file->cf_lines; | ||
324 | CronLine *line; | ||
325 | |||
326 | file->cf_has_running = 0; | ||
327 | file->cf_deleted = 1; | ||
328 | |||
329 | while ((line = *pline) != NULL) { | ||
330 | if (line->cl_pid > 0) { | ||
331 | file->cf_has_running = 1; | ||
332 | pline = &line->cl_next; | ||
333 | } else { | ||
334 | *pline = line->cl_next; | ||
335 | free(line->cl_cmd); | ||
336 | free(line); | ||
337 | } | ||
338 | } | ||
339 | if (file->cf_has_running == 0) { | ||
340 | *pfile = file->cf_next; | ||
341 | free(file->cf_username); | ||
342 | free(file); | ||
343 | continue; | ||
344 | } | ||
345 | } | ||
346 | pfile = &file->cf_next; | ||
347 | } | ||
348 | } | ||
349 | |||
350 | static void load_crontab(const char *fileName) | ||
454 | { | 351 | { |
455 | struct parser_t *parser; | 352 | struct parser_t *parser; |
456 | struct stat sbuf; | 353 | struct stat sbuf; |
@@ -460,23 +357,26 @@ static void SynchronizeFile(const char *fileName) | |||
460 | char *mailTo = NULL; | 357 | char *mailTo = NULL; |
461 | #endif | 358 | #endif |
462 | 359 | ||
463 | if (!fileName) | 360 | delete_cronfile(fileName); |
361 | |||
362 | if (!getpwnam(fileName)) { | ||
363 | crondlog(LVL7 "ignoring file '%s' (no such user)", fileName); | ||
464 | return; | 364 | return; |
365 | } | ||
465 | 366 | ||
466 | DeleteFile(fileName); | ||
467 | parser = config_open(fileName); | 367 | parser = config_open(fileName); |
468 | if (!parser) | 368 | if (!parser) |
469 | return; | 369 | return; |
470 | 370 | ||
471 | maxLines = (strcmp(fileName, "root") == 0) ? 65535 : MAXLINES; | 371 | maxLines = (strcmp(fileName, "root") == 0) ? 65535 : MAXLINES; |
472 | 372 | ||
473 | if (fstat(fileno(parser->fp), &sbuf) == 0 && sbuf.st_uid == DaemonUid) { | 373 | if (fstat(fileno(parser->fp), &sbuf) == 0 && sbuf.st_uid == DAEMON_UID) { |
474 | CronFile *file = xzalloc(sizeof(CronFile)); | 374 | CronFile *file = xzalloc(sizeof(CronFile)); |
475 | CronLine **pline; | 375 | CronLine **pline; |
476 | int n; | 376 | int n; |
477 | 377 | ||
478 | file->cf_User = xstrdup(fileName); | 378 | file->cf_username = xstrdup(fileName); |
479 | pline = &file->cf_LineBase; | 379 | pline = &file->cf_lines; |
480 | 380 | ||
481 | while (1) { | 381 | while (1) { |
482 | CronLine *line; | 382 | CronLine *line; |
@@ -503,11 +403,11 @@ static void SynchronizeFile(const char *fileName) | |||
503 | continue; | 403 | continue; |
504 | *pline = line = xzalloc(sizeof(*line)); | 404 | *pline = line = xzalloc(sizeof(*line)); |
505 | /* parse date ranges */ | 405 | /* parse date ranges */ |
506 | ParseField(file->cf_User, line->cl_Mins, 60, 0, NULL, tokens[0]); | 406 | ParseField(file->cf_username, line->cl_Mins, 60, 0, NULL, tokens[0]); |
507 | ParseField(file->cf_User, line->cl_Hrs, 24, 0, NULL, tokens[1]); | 407 | ParseField(file->cf_username, line->cl_Hrs, 24, 0, NULL, tokens[1]); |
508 | ParseField(file->cf_User, line->cl_Days, 32, 0, NULL, tokens[2]); | 408 | ParseField(file->cf_username, line->cl_Days, 32, 0, NULL, tokens[2]); |
509 | ParseField(file->cf_User, line->cl_Mons, 12, -1, MonAry, tokens[3]); | 409 | ParseField(file->cf_username, line->cl_Mons, 12, -1, MonAry, tokens[3]); |
510 | ParseField(file->cf_User, line->cl_Dow, 7, 0, DowAry, tokens[4]); | 410 | ParseField(file->cf_username, line->cl_Dow, 7, 0, DowAry, tokens[4]); |
511 | /* | 411 | /* |
512 | * fix days and dow - if one is not "*" and the other | 412 | * fix days and dow - if one is not "*" and the other |
513 | * is "*", the other is set to 0, and vise-versa | 413 | * is "*", the other is set to 0, and vise-versa |
@@ -515,20 +415,20 @@ static void SynchronizeFile(const char *fileName) | |||
515 | FixDayDow(line); | 415 | FixDayDow(line); |
516 | #if ENABLE_FEATURE_CROND_CALL_SENDMAIL | 416 | #if ENABLE_FEATURE_CROND_CALL_SENDMAIL |
517 | /* copy mailto (can be NULL) */ | 417 | /* copy mailto (can be NULL) */ |
518 | line->cl_MailTo = xstrdup(mailTo); | 418 | line->cl_mailto = xstrdup(mailTo); |
519 | #endif | 419 | #endif |
520 | /* copy command */ | 420 | /* copy command */ |
521 | line->cl_Shell = xstrdup(tokens[5]); | 421 | line->cl_cmd = xstrdup(tokens[5]); |
522 | if (DebugOpt) { | 422 | if (DebugOpt) { |
523 | crondlog(LVL5 " command:%s", tokens[5]); | 423 | crondlog(LVL5 " command:%s", tokens[5]); |
524 | } | 424 | } |
525 | pline = &line->cl_Next; | 425 | pline = &line->cl_next; |
526 | //bb_error_msg("M[%s]F[%s][%s][%s][%s][%s][%s]", mailTo, tokens[0], tokens[1], tokens[2], tokens[3], tokens[4], tokens[5]); | 426 | //bb_error_msg("M[%s]F[%s][%s][%s][%s][%s][%s]", mailTo, tokens[0], tokens[1], tokens[2], tokens[3], tokens[4], tokens[5]); |
527 | } | 427 | } |
528 | *pline = NULL; | 428 | *pline = NULL; |
529 | 429 | ||
530 | file->cf_Next = FileBase; | 430 | file->cf_next = G.cron_files; |
531 | FileBase = file; | 431 | G.cron_files = file; |
532 | 432 | ||
533 | if (maxLines == 0) { | 433 | if (maxLines == 0) { |
534 | crondlog(WARN9 "user %s: too many lines", fileName); | 434 | crondlog(WARN9 "user %s: too many lines", fileName); |
@@ -537,7 +437,7 @@ static void SynchronizeFile(const char *fileName) | |||
537 | config_close(parser); | 437 | config_close(parser); |
538 | } | 438 | } |
539 | 439 | ||
540 | static void CheckUpdates(void) | 440 | static void process_cron_update_file(void) |
541 | { | 441 | { |
542 | FILE *fi; | 442 | FILE *fi; |
543 | char buf[256]; | 443 | char buf[256]; |
@@ -547,36 +447,34 @@ static void CheckUpdates(void) | |||
547 | unlink(CRONUPDATE); | 447 | unlink(CRONUPDATE); |
548 | while (fgets(buf, sizeof(buf), fi) != NULL) { | 448 | while (fgets(buf, sizeof(buf), fi) != NULL) { |
549 | /* use first word only */ | 449 | /* use first word only */ |
550 | SynchronizeFile(strtok(buf, " \t\r\n")); | 450 | skip_non_whitespace(buf)[0] = '\0'; |
451 | load_crontab(buf); | ||
551 | } | 452 | } |
552 | fclose(fi); | 453 | fclose(fi); |
553 | } | 454 | } |
554 | } | 455 | } |
555 | 456 | ||
556 | static void SynchronizeDir(void) | 457 | static void rescan_crontab_dir(void) |
557 | { | 458 | { |
558 | CronFile *file; | 459 | CronFile *file; |
559 | /* Attempt to delete the database. */ | 460 | |
461 | /* Delete all files until we only have ones with running jobs (or none) */ | ||
560 | again: | 462 | again: |
561 | for (file = FileBase; file; file = file->cf_Next) { | 463 | for (file = G.cron_files; file; file = file->cf_next) { |
562 | if (!file->cf_Deleted) { | 464 | if (!file->cf_deleted) { |
563 | DeleteFile(file->cf_User); | 465 | delete_cronfile(file->cf_username); |
564 | goto again; | 466 | goto again; |
565 | } | 467 | } |
566 | } | 468 | } |
567 | 469 | ||
568 | /* | 470 | /* Remove cron update file */ |
569 | * Remove cron update file | ||
570 | * | ||
571 | * Re-chdir, in case directory was renamed & deleted, or otherwise | ||
572 | * screwed up. | ||
573 | * | ||
574 | * scan directory and add associated users | ||
575 | */ | ||
576 | unlink(CRONUPDATE); | 471 | unlink(CRONUPDATE); |
577 | if (chdir(CDir) < 0) { | 472 | /* Re-chdir, in case directory was renamed & deleted */ |
578 | crondlog(DIE9 "chdir(%s)", CDir); | 473 | if (chdir(G.crontab_dir_name) < 0) { |
474 | crondlog(DIE9 "chdir(%s)", G.crontab_dir_name); | ||
579 | } | 475 | } |
476 | |||
477 | /* Scan directory and add associated users */ | ||
580 | { | 478 | { |
581 | DIR *dir = opendir("."); | 479 | DIR *dir = opendir("."); |
582 | struct dirent *den; | 480 | struct dirent *den; |
@@ -587,184 +485,63 @@ static void SynchronizeDir(void) | |||
587 | if (strchr(den->d_name, '.') != NULL) { | 485 | if (strchr(den->d_name, '.') != NULL) { |
588 | continue; | 486 | continue; |
589 | } | 487 | } |
590 | if (getpwnam(den->d_name)) { | 488 | load_crontab(den->d_name); |
591 | SynchronizeFile(den->d_name); | ||
592 | } else { | ||
593 | crondlog(LVL7 "ignoring %s", den->d_name); | ||
594 | } | ||
595 | } | 489 | } |
596 | closedir(dir); | 490 | closedir(dir); |
597 | } | 491 | } |
598 | } | 492 | } |
599 | 493 | ||
600 | /* | 494 | #if SETENV_LEAKS |
601 | * DeleteFile() - delete user database | 495 | /* We set environment *before* vfork (because we want to use vfork), |
602 | * | 496 | * so we cannot use setenv() - repeated calls to setenv() may leak memory! |
603 | * Note: multiple entries for same user may exist if we were unable to | 497 | * Using putenv(), and freeing memory after unsetenv() won't leak */ |
604 | * completely delete a database due to running processes. | 498 | static void safe_setenv(char **pvar_val, const char *var, const char *val) |
605 | */ | ||
606 | static void DeleteFile(const char *userName) | ||
607 | { | ||
608 | CronFile **pfile = &FileBase; | ||
609 | CronFile *file; | ||
610 | |||
611 | while ((file = *pfile) != NULL) { | ||
612 | if (strcmp(userName, file->cf_User) == 0) { | ||
613 | CronLine **pline = &file->cf_LineBase; | ||
614 | CronLine *line; | ||
615 | |||
616 | file->cf_Running = 0; | ||
617 | file->cf_Deleted = 1; | ||
618 | |||
619 | while ((line = *pline) != NULL) { | ||
620 | if (line->cl_Pid > 0) { | ||
621 | file->cf_Running = 1; | ||
622 | pline = &line->cl_Next; | ||
623 | } else { | ||
624 | *pline = line->cl_Next; | ||
625 | free(line->cl_Shell); | ||
626 | free(line); | ||
627 | } | ||
628 | } | ||
629 | if (file->cf_Running == 0) { | ||
630 | *pfile = file->cf_Next; | ||
631 | free(file->cf_User); | ||
632 | free(file); | ||
633 | } else { | ||
634 | pfile = &file->cf_Next; | ||
635 | } | ||
636 | } else { | ||
637 | pfile = &file->cf_Next; | ||
638 | } | ||
639 | } | ||
640 | } | ||
641 | |||
642 | /* | ||
643 | * TestJobs() | ||
644 | * | ||
645 | * determine which jobs need to be run. Under normal conditions, the | ||
646 | * period is about a minute (one scan). Worst case it will be one | ||
647 | * hour (60 scans). | ||
648 | */ | ||
649 | static int TestJobs(time_t t1, time_t t2) | ||
650 | { | 499 | { |
651 | int nJobs = 0; | 500 | char *var_val = *pvar_val; |
652 | time_t t; | ||
653 | |||
654 | /* Find jobs > t1 and <= t2 */ | ||
655 | |||
656 | for (t = t1 - t1 % 60; t <= t2; t += 60) { | ||
657 | struct tm *ptm; | ||
658 | CronFile *file; | ||
659 | CronLine *line; | ||
660 | |||
661 | if (t <= t1) | ||
662 | continue; | ||
663 | 501 | ||
664 | ptm = localtime(&t); | 502 | if (var_val) { |
665 | for (file = FileBase; file; file = file->cf_Next) { | 503 | bb_unsetenv_and_free(var_val); |
666 | if (DebugOpt) | ||
667 | crondlog(LVL5 "file %s:", file->cf_User); | ||
668 | if (file->cf_Deleted) | ||
669 | continue; | ||
670 | for (line = file->cf_LineBase; line; line = line->cl_Next) { | ||
671 | if (DebugOpt) | ||
672 | crondlog(LVL5 " line %s", line->cl_Shell); | ||
673 | if (line->cl_Mins[ptm->tm_min] && line->cl_Hrs[ptm->tm_hour] | ||
674 | && (line->cl_Days[ptm->tm_mday] || line->cl_Dow[ptm->tm_wday]) | ||
675 | && line->cl_Mons[ptm->tm_mon] | ||
676 | ) { | ||
677 | if (DebugOpt) { | ||
678 | crondlog(LVL5 " job: %d %s", | ||
679 | (int)line->cl_Pid, line->cl_Shell); | ||
680 | } | ||
681 | if (line->cl_Pid > 0) { | ||
682 | crondlog(LVL8 "user %s: process already running: %s", | ||
683 | file->cf_User, line->cl_Shell); | ||
684 | } else if (line->cl_Pid == 0) { | ||
685 | line->cl_Pid = -1; | ||
686 | file->cf_Ready = 1; | ||
687 | ++nJobs; | ||
688 | } | ||
689 | } | ||
690 | } | ||
691 | } | ||
692 | } | 504 | } |
693 | return nJobs; | 505 | *pvar_val = xasprintf("%s=%s", var, val); |
506 | putenv(*pvar_val); | ||
694 | } | 507 | } |
508 | #endif | ||
695 | 509 | ||
696 | static void RunJobs(void) | 510 | static void set_env_vars(struct passwd *pas) |
697 | { | 511 | { |
698 | CronFile *file; | 512 | #if SETENV_LEAKS |
699 | CronLine *line; | 513 | safe_setenv(&G.env_var_user, "USER", pas->pw_name); |
700 | 514 | safe_setenv(&G.env_var_home, "HOME", pas->pw_dir); | |
701 | for (file = FileBase; file; file = file->cf_Next) { | 515 | /* if we want to set user's shell instead: */ |
702 | if (!file->cf_Ready) | 516 | /*safe_setenv(G.env_var_shell, "SHELL", pas->pw_shell);*/ |
703 | continue; | 517 | #else |
704 | 518 | xsetenv("USER", pas->pw_name); | |
705 | file->cf_Ready = 0; | 519 | xsetenv("HOME", pas->pw_dir); |
706 | for (line = file->cf_LineBase; line; line = line->cl_Next) { | 520 | #endif |
707 | if (line->cl_Pid >= 0) | 521 | /* currently, we use constant one: */ |
708 | continue; | 522 | /*setenv("SHELL", DEFAULT_SHELL, 1); - done earlier */ |
709 | |||
710 | RunJob(file->cf_User, line); | ||
711 | crondlog(LVL8 "USER %s pid %3d cmd %s", | ||
712 | file->cf_User, (int)line->cl_Pid, line->cl_Shell); | ||
713 | if (line->cl_Pid < 0) { | ||
714 | file->cf_Ready = 1; | ||
715 | } else if (line->cl_Pid > 0) { | ||
716 | file->cf_Running = 1; | ||
717 | } | ||
718 | } | ||
719 | } | ||
720 | } | 523 | } |
721 | 524 | ||
722 | /* | 525 | static void change_user(struct passwd *pas) |
723 | * CheckJobs() - check for job completion | ||
724 | * | ||
725 | * Check for job completion, return number of jobs still running after | ||
726 | * all done. | ||
727 | */ | ||
728 | static int CheckJobs(void) | ||
729 | { | 526 | { |
730 | CronFile *file; | 527 | /* careful: we're after vfork! */ |
731 | CronLine *line; | 528 | change_identity(pas); /* - initgroups, setgid, setuid */ |
732 | int nStillRunning = 0; | 529 | if (chdir(pas->pw_dir) < 0) { |
733 | 530 | crondlog(WARN9 "chdir(%s)", pas->pw_dir); | |
734 | for (file = FileBase; file; file = file->cf_Next) { | 531 | if (chdir(TMPDIR) < 0) { |
735 | if (file->cf_Running) { | 532 | crondlog(DIE9 "chdir(%s)", TMPDIR); /* exits */ |
736 | file->cf_Running = 0; | ||
737 | |||
738 | for (line = file->cf_LineBase; line; line = line->cl_Next) { | ||
739 | int status, r; | ||
740 | if (line->cl_Pid <= 0) | ||
741 | continue; | ||
742 | |||
743 | r = waitpid(line->cl_Pid, &status, WNOHANG); | ||
744 | if (r < 0 || r == line->cl_Pid) { | ||
745 | EndJob(file->cf_User, line); | ||
746 | if (line->cl_Pid) { | ||
747 | file->cf_Running = 1; | ||
748 | } | ||
749 | } else if (r == 0) { | ||
750 | file->cf_Running = 1; | ||
751 | } | ||
752 | } | ||
753 | } | 533 | } |
754 | nStillRunning += file->cf_Running; | ||
755 | } | 534 | } |
756 | return nStillRunning; | ||
757 | } | 535 | } |
758 | 536 | ||
759 | #if ENABLE_FEATURE_CROND_CALL_SENDMAIL | ||
760 | |||
761 | // TODO: sendmail should be _run-time_ option, not compile-time! | 537 | // TODO: sendmail should be _run-time_ option, not compile-time! |
538 | #if ENABLE_FEATURE_CROND_CALL_SENDMAIL | ||
762 | 539 | ||
763 | static void | 540 | static pid_t |
764 | ForkJob(const char *user, CronLine *line, int mailFd, | 541 | fork_job(const char *user, int mailFd, |
765 | const char *prog, const char *cmd, const char *arg, | 542 | const char *prog, |
766 | const char *mail_filename) | 543 | const char *shell_cmd /* if NULL, we run sendmail */ |
767 | { | 544 | ) { |
768 | struct passwd *pas; | 545 | struct passwd *pas; |
769 | pid_t pid; | 546 | pid_t pid; |
770 | 547 | ||
@@ -774,48 +551,36 @@ ForkJob(const char *user, CronLine *line, int mailFd, | |||
774 | crondlog(WARN9 "can't get uid for %s", user); | 551 | crondlog(WARN9 "can't get uid for %s", user); |
775 | goto err; | 552 | goto err; |
776 | } | 553 | } |
777 | SetEnv(pas); | 554 | set_env_vars(pas); |
778 | 555 | ||
779 | pid = vfork(); | 556 | pid = vfork(); |
780 | if (pid == 0) { | 557 | if (pid == 0) { |
781 | /* CHILD */ | 558 | /* CHILD */ |
782 | /* change running state to the user in question */ | 559 | /* initgroups, setgid, setuid, and chdir to home or TMPDIR */ |
783 | ChangeUser(pas); | 560 | change_user(pas); |
784 | if (DebugOpt) { | 561 | if (DebugOpt) { |
785 | crondlog(LVL5 "child running %s", prog); | 562 | crondlog(LVL5 "child running %s", prog); |
786 | } | 563 | } |
787 | if (mailFd >= 0) { | 564 | if (mailFd >= 0) { |
788 | xmove_fd(mailFd, mail_filename ? 1 : 0); | 565 | xmove_fd(mailFd, shell_cmd ? 1 : 0); |
789 | dup2(1, 2); | 566 | dup2(1, 2); |
790 | } | 567 | } |
791 | /* crond 3.0pl1-100 puts tasks in separate process groups */ | 568 | /* crond 3.0pl1-100 puts tasks in separate process groups */ |
792 | bb_setpgrp(); | 569 | bb_setpgrp(); |
793 | execlp(prog, prog, cmd, arg, (char *) NULL); | 570 | execlp(prog, prog, (shell_cmd ? "-c" : SENDMAIL_ARGS), shell_cmd, (char *) NULL); |
794 | crondlog(ERR20 "can't exec, user %s cmd %s %s %s", user, prog, cmd, arg); | 571 | crondlog(ERR20 "can't execute '%s' for user %s", prog, user); |
795 | if (mail_filename) { | 572 | if (shell_cmd) { |
796 | fdprintf(1, "Exec failed: %s -c %s\n", prog, arg); | 573 | fdprintf(1, "Exec failed: %s -c %s\n", prog, shell_cmd); |
797 | } | 574 | } |
798 | _exit(EXIT_SUCCESS); | 575 | _exit(EXIT_SUCCESS); |
799 | } | 576 | } |
800 | 577 | ||
801 | line->cl_Pid = pid; | ||
802 | if (pid < 0) { | 578 | if (pid < 0) { |
803 | /* FORK FAILED */ | 579 | /* FORK FAILED */ |
804 | crondlog(ERR20 "can't vfork"); | 580 | crondlog(ERR20 "can't vfork"); |
805 | err: | 581 | err: |
806 | line->cl_Pid = 0; | 582 | pid = 0; |
807 | if (mail_filename) { | 583 | } /* else: PARENT, FORK SUCCESS */ |
808 | unlink(mail_filename); | ||
809 | } | ||
810 | } else if (mail_filename) { | ||
811 | /* PARENT, FORK SUCCESS | ||
812 | * rename mail-file based on pid of process | ||
813 | */ | ||
814 | char mailFile2[128]; | ||
815 | |||
816 | snprintf(mailFile2, sizeof(mailFile2), "%s/cron.%s.%d", TMPDIR, user, pid); | ||
817 | rename(mail_filename, mailFile2); // TODO: xrename? | ||
818 | } | ||
819 | 584 | ||
820 | /* | 585 | /* |
821 | * Close the mail file descriptor.. we can't just leave it open in | 586 | * Close the mail file descriptor.. we can't just leave it open in |
@@ -824,112 +589,120 @@ ForkJob(const char *user, CronLine *line, int mailFd, | |||
824 | if (mailFd >= 0) { | 589 | if (mailFd >= 0) { |
825 | close(mailFd); | 590 | close(mailFd); |
826 | } | 591 | } |
592 | return pid; | ||
827 | } | 593 | } |
828 | 594 | ||
829 | static void RunJob(const char *user, CronLine *line) | 595 | static void start_one_job(const char *user, CronLine *line) |
830 | { | 596 | { |
831 | char mailFile[128]; | 597 | char mailFile[128]; |
832 | int mailFd = -1; | 598 | int mailFd = -1; |
833 | 599 | ||
834 | line->cl_Pid = 0; | 600 | line->cl_pid = 0; |
835 | line->cl_MailFlag = 0; | 601 | line->cl_empty_mail_size = 0; |
836 | 602 | ||
837 | if (line->cl_MailTo) { | 603 | if (line->cl_mailto) { |
838 | /* open mail file - owner root so nobody can screw with it. */ | 604 | /* Open mail file (owner is root so nobody can screw with it) */ |
839 | snprintf(mailFile, sizeof(mailFile), "%s/cron.%s.%d", TMPDIR, user, getpid()); | 605 | snprintf(mailFile, sizeof(mailFile), "%s/cron.%s.%d", TMPDIR, user, getpid()); |
840 | mailFd = open(mailFile, O_CREAT | O_TRUNC | O_WRONLY | O_EXCL | O_APPEND, 0600); | 606 | mailFd = open(mailFile, O_CREAT | O_TRUNC | O_WRONLY | O_EXCL | O_APPEND, 0600); |
841 | 607 | ||
842 | if (mailFd >= 0) { | 608 | if (mailFd >= 0) { |
843 | line->cl_MailFlag = 1; | 609 | fdprintf(mailFd, "To: %s\nSubject: cron: %s\n\n", line->cl_mailto, |
844 | fdprintf(mailFd, "To: %s\nSubject: cron: %s\n\n", line->cl_MailTo, | 610 | line->cl_cmd); |
845 | line->cl_Shell); | 611 | line->cl_empty_mail_size = lseek(mailFd, 0, SEEK_CUR); |
846 | line->cl_MailPos = lseek(mailFd, 0, SEEK_CUR); | ||
847 | } else { | 612 | } else { |
848 | crondlog(ERR20 "can't create mail file %s for user %s, " | 613 | crondlog(ERR20 "can't create mail file %s for user %s, " |
849 | "discarding output", mailFile, user); | 614 | "discarding output", mailFile, user); |
850 | } | 615 | } |
851 | } | 616 | } |
852 | 617 | ||
853 | ForkJob(user, line, mailFd, DEFAULT_SHELL, "-c", line->cl_Shell, mailFile); | 618 | line->cl_pid = fork_job(user, mailFd, DEFAULT_SHELL, line->cl_cmd); |
619 | if (mailFd >= 0) { | ||
620 | if (line->cl_pid <= 0) { | ||
621 | unlink(mailFile); | ||
622 | } else { | ||
623 | /* rename mail-file based on pid of process */ | ||
624 | char *mailFile2 = xasprintf("%s/cron.%s.%d", TMPDIR, user, (int)line->cl_pid); | ||
625 | rename(mailFile, mailFile2); // TODO: xrename? | ||
626 | free(mailFile2); | ||
627 | } | ||
628 | } | ||
854 | } | 629 | } |
855 | 630 | ||
856 | /* | 631 | /* |
857 | * EndJob - called when job terminates and when mail terminates | 632 | * process_finished_job - called when job terminates and when mail terminates |
858 | */ | 633 | */ |
859 | static void EndJob(const char *user, CronLine *line) | 634 | static void process_finished_job(const char *user, CronLine *line) |
860 | { | 635 | { |
636 | pid_t pid; | ||
861 | int mailFd; | 637 | int mailFd; |
862 | char mailFile[128]; | 638 | char mailFile[128]; |
863 | struct stat sbuf; | 639 | struct stat sbuf; |
864 | 640 | ||
865 | /* No job */ | 641 | pid = line->cl_pid; |
866 | if (line->cl_Pid <= 0) { | 642 | line->cl_pid = 0; |
867 | line->cl_Pid = 0; | 643 | if (pid <= 0) { |
644 | /* No job */ | ||
868 | return; | 645 | return; |
869 | } | 646 | } |
870 | 647 | if (line->cl_empty_mail_size <= 0) { | |
871 | /* | 648 | /* End of job and no mail file, or end of sendmail job */ |
872 | * End of job and no mail file | ||
873 | * End of sendmail job | ||
874 | */ | ||
875 | snprintf(mailFile, sizeof(mailFile), "%s/cron.%s.%d", TMPDIR, user, line->cl_Pid); | ||
876 | line->cl_Pid = 0; | ||
877 | |||
878 | if (line->cl_MailFlag == 0) { | ||
879 | return; | 649 | return; |
880 | } | 650 | } |
881 | line->cl_MailFlag = 0; | ||
882 | 651 | ||
883 | /* | 652 | /* |
884 | * End of primary job - check for mail file. If size has increased and | 653 | * End of primary job - check for mail file. |
885 | * the file is still valid, we sendmail it. | 654 | * If size has changed and the file is still valid, we send it. |
886 | */ | 655 | */ |
656 | snprintf(mailFile, sizeof(mailFile), "%s/cron.%s.%d", TMPDIR, user, (int)pid); | ||
887 | mailFd = open(mailFile, O_RDONLY); | 657 | mailFd = open(mailFile, O_RDONLY); |
888 | unlink(mailFile); | 658 | unlink(mailFile); |
889 | if (mailFd < 0) { | 659 | if (mailFd < 0) { |
890 | return; | 660 | return; |
891 | } | 661 | } |
892 | 662 | ||
893 | if (fstat(mailFd, &sbuf) < 0 || sbuf.st_uid != DaemonUid | 663 | if (fstat(mailFd, &sbuf) < 0 |
894 | || sbuf.st_nlink != 0 || sbuf.st_size == line->cl_MailPos | 664 | || sbuf.st_uid != DAEMON_UID |
665 | || sbuf.st_nlink != 0 | ||
666 | || sbuf.st_size == line->cl_empty_mail_size | ||
895 | || !S_ISREG(sbuf.st_mode) | 667 | || !S_ISREG(sbuf.st_mode) |
896 | ) { | 668 | ) { |
897 | close(mailFd); | 669 | close(mailFd); |
898 | return; | 670 | return; |
899 | } | 671 | } |
900 | if (line->cl_MailTo) | 672 | line->cl_empty_mail_size = 0; |
901 | ForkJob(user, line, mailFd, SENDMAIL, SENDMAIL_ARGS, NULL); | 673 | /* if (line->cl_mailto) - always true if cl_empty_mail_size was nonzero */ |
674 | line->cl_pid = fork_job(user, mailFd, SENDMAIL, NULL); | ||
902 | } | 675 | } |
903 | 676 | ||
904 | #else /* crond without sendmail */ | 677 | #else /* !ENABLE_FEATURE_CROND_CALL_SENDMAIL */ |
905 | 678 | ||
906 | static void RunJob(const char *user, CronLine *line) | 679 | static void start_one_job(const char *user, CronLine *line) |
907 | { | 680 | { |
908 | struct passwd *pas; | 681 | struct passwd *pas; |
909 | pid_t pid; | 682 | pid_t pid; |
910 | 683 | ||
911 | /* prepare things before vfork */ | ||
912 | pas = getpwnam(user); | 684 | pas = getpwnam(user); |
913 | if (!pas) { | 685 | if (!pas) { |
914 | crondlog(WARN9 "can't get uid for %s", user); | 686 | crondlog(WARN9 "can't get uid for %s", user); |
915 | goto err; | 687 | goto err; |
916 | } | 688 | } |
917 | SetEnv(pas); | ||
918 | 689 | ||
919 | /* fork as the user in question and run program */ | 690 | /* Prepare things before vfork */ |
691 | set_env_vars(pas); | ||
692 | |||
693 | /* Fork as the user in question and run program */ | ||
920 | pid = vfork(); | 694 | pid = vfork(); |
921 | if (pid == 0) { | 695 | if (pid == 0) { |
922 | /* CHILD */ | 696 | /* CHILD */ |
923 | /* change running state to the user in question */ | 697 | /* initgroups, setgid, setuid, and chdir to home or TMPDIR */ |
924 | ChangeUser(pas); | 698 | change_user(pas); |
925 | if (DebugOpt) { | 699 | if (DebugOpt) { |
926 | crondlog(LVL5 "child running %s", DEFAULT_SHELL); | 700 | crondlog(LVL5 "child running %s", DEFAULT_SHELL); |
927 | } | 701 | } |
928 | /* crond 3.0pl1-100 puts tasks in separate process groups */ | 702 | /* crond 3.0pl1-100 puts tasks in separate process groups */ |
929 | bb_setpgrp(); | 703 | bb_setpgrp(); |
930 | execl(DEFAULT_SHELL, DEFAULT_SHELL, "-c", line->cl_Shell, (char *) NULL); | 704 | execl(DEFAULT_SHELL, DEFAULT_SHELL, "-c", line->cl_cmd, (char *) NULL); |
931 | crondlog(ERR20 "can't exec, user %s cmd %s %s %s", user, | 705 | crondlog(ERR20 "can't execute '%s' for user %s", DEFAULT_SHELL, user); |
932 | DEFAULT_SHELL, "-c", line->cl_Shell); | ||
933 | _exit(EXIT_SUCCESS); | 706 | _exit(EXIT_SUCCESS); |
934 | } | 707 | } |
935 | if (pid < 0) { | 708 | if (pid < 0) { |
@@ -938,7 +711,231 @@ static void RunJob(const char *user, CronLine *line) | |||
938 | err: | 711 | err: |
939 | pid = 0; | 712 | pid = 0; |
940 | } | 713 | } |
941 | line->cl_Pid = pid; | 714 | line->cl_pid = pid; |
942 | } | 715 | } |
943 | 716 | ||
944 | #endif /* ENABLE_FEATURE_CROND_CALL_SENDMAIL */ | 717 | #define process_finished_job(user, line) ((line)->cl_pid = 0) |
718 | |||
719 | #endif /* !ENABLE_FEATURE_CROND_CALL_SENDMAIL */ | ||
720 | |||
721 | /* | ||
722 | * Determine which jobs need to be run. Under normal conditions, the | ||
723 | * period is about a minute (one scan). Worst case it will be one | ||
724 | * hour (60 scans). | ||
725 | */ | ||
726 | static void flag_starting_jobs(time_t t1, time_t t2) | ||
727 | { | ||
728 | time_t t; | ||
729 | |||
730 | /* Find jobs > t1 and <= t2 */ | ||
731 | |||
732 | for (t = t1 - t1 % 60; t <= t2; t += 60) { | ||
733 | struct tm *ptm; | ||
734 | CronFile *file; | ||
735 | CronLine *line; | ||
736 | |||
737 | if (t <= t1) | ||
738 | continue; | ||
739 | |||
740 | ptm = localtime(&t); | ||
741 | for (file = G.cron_files; file; file = file->cf_next) { | ||
742 | if (DebugOpt) | ||
743 | crondlog(LVL5 "file %s:", file->cf_username); | ||
744 | if (file->cf_deleted) | ||
745 | continue; | ||
746 | for (line = file->cf_lines; line; line = line->cl_next) { | ||
747 | if (DebugOpt) | ||
748 | crondlog(LVL5 " line %s", line->cl_cmd); | ||
749 | if (line->cl_Mins[ptm->tm_min] | ||
750 | && line->cl_Hrs[ptm->tm_hour] | ||
751 | && (line->cl_Days[ptm->tm_mday] || line->cl_Dow[ptm->tm_wday]) | ||
752 | && line->cl_Mons[ptm->tm_mon] | ||
753 | ) { | ||
754 | if (DebugOpt) { | ||
755 | crondlog(LVL5 " job: %d %s", | ||
756 | (int)line->cl_pid, line->cl_cmd); | ||
757 | } | ||
758 | if (line->cl_pid > 0) { | ||
759 | crondlog(LVL8 "user %s: process already running: %s", | ||
760 | file->cf_username, line->cl_cmd); | ||
761 | } else if (line->cl_pid == 0) { | ||
762 | line->cl_pid = -1; | ||
763 | file->cf_wants_starting = 1; | ||
764 | } | ||
765 | } | ||
766 | } | ||
767 | } | ||
768 | } | ||
769 | } | ||
770 | |||
771 | static void start_jobs(void) | ||
772 | { | ||
773 | CronFile *file; | ||
774 | CronLine *line; | ||
775 | |||
776 | for (file = G.cron_files; file; file = file->cf_next) { | ||
777 | if (!file->cf_wants_starting) | ||
778 | continue; | ||
779 | |||
780 | file->cf_wants_starting = 0; | ||
781 | for (line = file->cf_lines; line; line = line->cl_next) { | ||
782 | pid_t pid; | ||
783 | if (line->cl_pid >= 0) | ||
784 | continue; | ||
785 | |||
786 | start_one_job(file->cf_username, line); | ||
787 | pid = line->cl_pid; | ||
788 | crondlog(LVL8 "USER %s pid %3d cmd %s", | ||
789 | file->cf_username, (int)pid, line->cl_cmd); | ||
790 | if (pid < 0) { | ||
791 | file->cf_wants_starting = 1; | ||
792 | } | ||
793 | if (pid > 0) { | ||
794 | file->cf_has_running = 1; | ||
795 | } | ||
796 | } | ||
797 | } | ||
798 | } | ||
799 | |||
800 | /* | ||
801 | * Check for job completion, return number of jobs still running after | ||
802 | * all done. | ||
803 | */ | ||
804 | static int check_completions(void) | ||
805 | { | ||
806 | CronFile *file; | ||
807 | CronLine *line; | ||
808 | int num_still_running = 0; | ||
809 | |||
810 | for (file = G.cron_files; file; file = file->cf_next) { | ||
811 | if (!file->cf_has_running) | ||
812 | continue; | ||
813 | |||
814 | file->cf_has_running = 0; | ||
815 | for (line = file->cf_lines; line; line = line->cl_next) { | ||
816 | int r; | ||
817 | |||
818 | if (line->cl_pid <= 0) | ||
819 | continue; | ||
820 | |||
821 | r = waitpid(line->cl_pid, NULL, WNOHANG); | ||
822 | if (r < 0 || r == line->cl_pid) { | ||
823 | process_finished_job(file->cf_username, line); | ||
824 | if (line->cl_pid == 0) { | ||
825 | /* sendmail was not started for it */ | ||
826 | continue; | ||
827 | } | ||
828 | /* else: sendmail was started, job is still running, fall thru */ | ||
829 | } | ||
830 | /* else: r == 0: "process is still running" */ | ||
831 | file->cf_has_running = 1; | ||
832 | } | ||
833 | //FIXME: if !file->cf_has_running && file->deleted: delete it! | ||
834 | //otherwise deleted entries will stay forever, right? | ||
835 | num_still_running += file->cf_has_running; | ||
836 | } | ||
837 | return num_still_running; | ||
838 | } | ||
839 | |||
840 | int crond_main(int argc, char **argv) MAIN_EXTERNALLY_VISIBLE; | ||
841 | int crond_main(int argc UNUSED_PARAM, char **argv) | ||
842 | { | ||
843 | time_t t2; | ||
844 | int rescan; | ||
845 | int sleep_time; | ||
846 | unsigned opts; | ||
847 | |||
848 | INIT_G(); | ||
849 | |||
850 | /* "-b after -f is ignored", and so on for every pair a-b */ | ||
851 | opt_complementary = "f-b:b-f:S-L:L-S" IF_FEATURE_CROND_D(":d-l") | ||
852 | ":l+:d+"; /* -l and -d have numeric param */ | ||
853 | opts = getopt32(argv, "l:L:fbSc:" IF_FEATURE_CROND_D("d:"), | ||
854 | &G.log_level, &G.log_filename, &G.crontab_dir_name | ||
855 | IF_FEATURE_CROND_D(,&G.log_level)); | ||
856 | /* both -d N and -l N set the same variable: G.log_level */ | ||
857 | |||
858 | if (!(opts & OPT_f)) { | ||
859 | /* close stdin, stdout, stderr. | ||
860 | * close unused descriptors - don't need them. */ | ||
861 | bb_daemonize_or_rexec(DAEMON_CLOSE_EXTRA_FDS, argv); | ||
862 | } | ||
863 | |||
864 | if (!(opts & OPT_d) && G.log_filename == NULL) { | ||
865 | /* logging to syslog */ | ||
866 | openlog(applet_name, LOG_CONS | LOG_PID, LOG_CRON); | ||
867 | logmode = LOGMODE_SYSLOG; | ||
868 | } | ||
869 | |||
870 | xchdir(G.crontab_dir_name); | ||
871 | //signal(SIGHUP, SIG_IGN); /* ? original crond dies on HUP... */ | ||
872 | xsetenv("SHELL", DEFAULT_SHELL); /* once, for all future children */ | ||
873 | crondlog(LVL8 "crond (busybox "BB_VER") started, log level %d", G.log_level); | ||
874 | rescan_crontab_dir(); | ||
875 | write_pidfile("/var/run/crond.pid"); | ||
876 | |||
877 | /* Main loop */ | ||
878 | t2 = time(NULL); | ||
879 | rescan = 60; | ||
880 | sleep_time = 60; | ||
881 | for (;;) { | ||
882 | struct stat sbuf; | ||
883 | time_t t1; | ||
884 | long dt; | ||
885 | |||
886 | t1 = t2; | ||
887 | |||
888 | /* Synchronize to 1 minute, minimum 1 second */ | ||
889 | sleep(sleep_time - (time(NULL) % sleep_time) + 1); | ||
890 | |||
891 | t2 = time(NULL); | ||
892 | dt = (long)t2 - (long)t1; | ||
893 | |||
894 | /* | ||
895 | * The file 'cron.update' is checked to determine new cron | ||
896 | * jobs. The directory is rescanned once an hour to deal | ||
897 | * with any screwups. | ||
898 | * | ||
899 | * Check for time jump. Disparities over an hour either way | ||
900 | * result in resynchronization. A negative disparity | ||
901 | * less than an hour causes us to effectively sleep until we | ||
902 | * match the original time (i.e. no re-execution of jobs that | ||
903 | * have just been run). A positive disparity less than | ||
904 | * an hour causes intermediate jobs to be run, but only once | ||
905 | * in the worst case. | ||
906 | * | ||
907 | * When running jobs, the inequality used is greater but not | ||
908 | * equal to t1, and less then or equal to t2. | ||
909 | */ | ||
910 | if (stat(G.crontab_dir_name, &sbuf) != 0) | ||
911 | sbuf.st_mtime = 0; /* force update (once) if dir was deleted */ | ||
912 | if (G.crontab_dir_mtime != sbuf.st_mtime) { | ||
913 | G.crontab_dir_mtime = sbuf.st_mtime; | ||
914 | rescan = 1; | ||
915 | } | ||
916 | if (--rescan == 0) { | ||
917 | rescan = 60; | ||
918 | rescan_crontab_dir(); | ||
919 | } | ||
920 | process_cron_update_file(); | ||
921 | if (DebugOpt) | ||
922 | crondlog(LVL5 "wakeup dt=%ld", dt); | ||
923 | if (dt < -60 * 60 || dt > 60 * 60) { | ||
924 | crondlog(WARN9 "time disparity of %ld minutes detected", dt / 60); | ||
925 | /* and we do not run any jobs in this case */ | ||
926 | } else if (dt > 0) { | ||
927 | /* Usual case: time advances forward, as expected */ | ||
928 | flag_starting_jobs(t1, t2); | ||
929 | start_jobs(); | ||
930 | if (check_completions() > 0) { | ||
931 | /* some jobs are still running */ | ||
932 | sleep_time = 10; | ||
933 | } else { | ||
934 | sleep_time = 60; | ||
935 | } | ||
936 | } | ||
937 | /* else: time jumped back, do not run any jobs */ | ||
938 | } /* for (;;) */ | ||
939 | |||
940 | return 0; /* not reached */ | ||
941 | } | ||