crond.c 23 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940
  1. /* vi: set sw=4 ts=4: */
  2. /*
  3. * crond -d[#] -c <crondir> -f -b
  4. *
  5. * run as root, but NOT setuid root
  6. *
  7. * Copyright 1994 Matthew Dillon (dillon@apollo.west.oic.com)
  8. * (version 2.3.2)
  9. * Vladimir Oleynik <dzo@simtreas.ru> (C) 2002
  10. *
  11. * Licensed under GPLv2 or later, see file LICENSE in this source tree.
  12. */
  13. #include "libbb.h"
  14. #include <syslog.h>
  15. /* glibc frees previous setenv'ed value when we do next setenv()
  16. * of the same variable. uclibc does not do this! */
  17. #if (defined(__GLIBC__) && !defined(__UCLIBC__)) /* || OTHER_SAFE_LIBC... */
  18. # define SETENV_LEAKS 0
  19. #else
  20. # define SETENV_LEAKS 1
  21. #endif
  22. #define TMPDIR CONFIG_FEATURE_CROND_DIR
  23. #define CRONTABS CONFIG_FEATURE_CROND_DIR "/crontabs"
  24. #ifndef SENDMAIL
  25. # define SENDMAIL "sendmail"
  26. #endif
  27. #ifndef SENDMAIL_ARGS
  28. # define SENDMAIL_ARGS "-ti"
  29. #endif
  30. #ifndef CRONUPDATE
  31. # define CRONUPDATE "cron.update"
  32. #endif
  33. #ifndef MAXLINES
  34. # define MAXLINES 256 /* max lines in non-root crontabs */
  35. #endif
  36. typedef struct CronFile {
  37. struct CronFile *cf_next;
  38. struct CronLine *cf_lines;
  39. char *cf_username;
  40. smallint cf_wants_starting; /* bool: one or more jobs ready */
  41. smallint cf_has_running; /* bool: one or more jobs running */
  42. smallint cf_deleted; /* marked for deletion (but still has running jobs) */
  43. } CronFile;
  44. typedef struct CronLine {
  45. struct CronLine *cl_next;
  46. char *cl_cmd; /* shell command */
  47. pid_t cl_pid; /* >0:running, <0:needs to be started in this minute, 0:dormant */
  48. #if ENABLE_FEATURE_CROND_CALL_SENDMAIL
  49. int cl_empty_mail_size; /* size of mail header only, 0 if no mailfile */
  50. char *cl_mailto; /* whom to mail results, may be NULL */
  51. #endif
  52. /* ordered by size, not in natural order. makes code smaller: */
  53. char cl_Dow[7]; /* 0-6, beginning sunday */
  54. char cl_Mons[12]; /* 0-11 */
  55. char cl_Hrs[24]; /* 0-23 */
  56. char cl_Days[32]; /* 1-31 */
  57. char cl_Mins[60]; /* 0-59 */
  58. } CronLine;
  59. #define DAEMON_UID 0
  60. enum {
  61. OPT_l = (1 << 0),
  62. OPT_L = (1 << 1),
  63. OPT_f = (1 << 2),
  64. OPT_b = (1 << 3),
  65. OPT_S = (1 << 4),
  66. OPT_c = (1 << 5),
  67. OPT_d = (1 << 6) * ENABLE_FEATURE_CROND_D,
  68. };
  69. #if ENABLE_FEATURE_CROND_D
  70. # define DebugOpt (option_mask32 & OPT_d)
  71. #else
  72. # define DebugOpt 0
  73. #endif
  74. struct globals {
  75. unsigned log_level; /* = 8; */
  76. time_t crontab_dir_mtime;
  77. const char *log_filename;
  78. const char *crontab_dir_name; /* = CRONTABS; */
  79. CronFile *cron_files;
  80. #if SETENV_LEAKS
  81. char *env_var_user;
  82. char *env_var_home;
  83. #endif
  84. } FIX_ALIASING;
  85. #define G (*(struct globals*)&bb_common_bufsiz1)
  86. #define INIT_G() do { \
  87. G.log_level = 8; \
  88. G.crontab_dir_name = CRONTABS; \
  89. } while (0)
  90. /* 0 is the most verbose, default 8 */
  91. #define LVL5 "\x05"
  92. #define LVL7 "\x07"
  93. #define LVL8 "\x08"
  94. #define WARN9 "\x49"
  95. #define DIE9 "\xc9"
  96. /* level >= 20 is "error" */
  97. #define ERR20 "\x14"
  98. static void crondlog(const char *ctl, ...) __attribute__ ((format (printf, 1, 2)));
  99. static void crondlog(const char *ctl, ...)
  100. {
  101. va_list va;
  102. int level = (ctl[0] & 0x1f);
  103. va_start(va, ctl);
  104. if (level >= (int)G.log_level) {
  105. /* Debug mode: all to (non-redirected) stderr, */
  106. /* Syslog mode: all to syslog (logmode = LOGMODE_SYSLOG), */
  107. if (!DebugOpt && G.log_filename) {
  108. /* Otherwise (log to file): we reopen log file at every write: */
  109. int logfd = open_or_warn(G.log_filename, O_WRONLY | O_CREAT | O_APPEND);
  110. if (logfd >= 0)
  111. xmove_fd(logfd, STDERR_FILENO);
  112. }
  113. /* When we log to syslog, level > 8 is logged at LOG_ERR
  114. * syslog level, level <= 8 is logged at LOG_INFO. */
  115. if (level > 8) {
  116. bb_verror_msg(ctl + 1, va, /* strerr: */ NULL);
  117. } else {
  118. char *msg = NULL;
  119. vasprintf(&msg, ctl + 1, va);
  120. bb_info_msg("%s: %s", applet_name, msg);
  121. free(msg);
  122. }
  123. }
  124. va_end(va);
  125. if (ctl[0] & 0x80)
  126. exit(20);
  127. }
  128. static const char DowAry[] ALIGN1 =
  129. "sun""mon""tue""wed""thu""fri""sat"
  130. /* "Sun""Mon""Tue""Wed""Thu""Fri""Sat" */
  131. ;
  132. static const char MonAry[] ALIGN1 =
  133. "jan""feb""mar""apr""may""jun""jul""aug""sep""oct""nov""dec"
  134. /* "Jan""Feb""Mar""Apr""May""Jun""Jul""Aug""Sep""Oct""Nov""Dec" */
  135. ;
  136. static void ParseField(char *user, char *ary, int modvalue, int off,
  137. const char *names, char *ptr)
  138. /* 'names' is a pointer to a set of 3-char abbreviations */
  139. {
  140. char *base = ptr;
  141. int n1 = -1;
  142. int n2 = -1;
  143. // this can't happen due to config_read()
  144. /*if (base == NULL)
  145. return;*/
  146. while (1) {
  147. int skip = 0;
  148. /* Handle numeric digit or symbol or '*' */
  149. if (*ptr == '*') {
  150. n1 = 0; /* everything will be filled */
  151. n2 = modvalue - 1;
  152. skip = 1;
  153. ++ptr;
  154. } else if (isdigit(*ptr)) {
  155. char *endp;
  156. if (n1 < 0) {
  157. n1 = strtol(ptr, &endp, 10) + off;
  158. } else {
  159. n2 = strtol(ptr, &endp, 10) + off;
  160. }
  161. ptr = endp; /* gcc likes temp var for &endp */
  162. skip = 1;
  163. } else if (names) {
  164. int i;
  165. for (i = 0; names[i]; i += 3) {
  166. /* was using strncmp before... */
  167. if (strncasecmp(ptr, &names[i], 3) == 0) {
  168. ptr += 3;
  169. if (n1 < 0) {
  170. n1 = i / 3;
  171. } else {
  172. n2 = i / 3;
  173. }
  174. skip = 1;
  175. break;
  176. }
  177. }
  178. }
  179. /* handle optional range '-' */
  180. if (skip == 0) {
  181. goto err;
  182. }
  183. if (*ptr == '-' && n2 < 0) {
  184. ++ptr;
  185. continue;
  186. }
  187. /*
  188. * collapse single-value ranges, handle skipmark, and fill
  189. * in the character array appropriately.
  190. */
  191. if (n2 < 0) {
  192. n2 = n1;
  193. }
  194. if (*ptr == '/') {
  195. char *endp;
  196. skip = strtol(ptr + 1, &endp, 10);
  197. ptr = endp; /* gcc likes temp var for &endp */
  198. }
  199. /*
  200. * fill array, using a failsafe is the easiest way to prevent
  201. * an endless loop
  202. */
  203. {
  204. int s0 = 1;
  205. int failsafe = 1024;
  206. --n1;
  207. do {
  208. n1 = (n1 + 1) % modvalue;
  209. if (--s0 == 0) {
  210. ary[n1 % modvalue] = 1;
  211. s0 = skip;
  212. }
  213. if (--failsafe == 0) {
  214. goto err;
  215. }
  216. } while (n1 != n2);
  217. }
  218. if (*ptr != ',') {
  219. break;
  220. }
  221. ++ptr;
  222. n1 = -1;
  223. n2 = -1;
  224. }
  225. if (*ptr) {
  226. err:
  227. crondlog(WARN9 "user %s: parse error at %s", user, base);
  228. return;
  229. }
  230. if (DebugOpt && (G.log_level <= 5)) { /* like LVL5 */
  231. /* can't use crondlog, it inserts '\n' */
  232. int i;
  233. for (i = 0; i < modvalue; ++i)
  234. fprintf(stderr, "%d", (unsigned char)ary[i]);
  235. bb_putchar_stderr('\n');
  236. }
  237. }
  238. static void FixDayDow(CronLine *line)
  239. {
  240. unsigned i;
  241. int weekUsed = 0;
  242. int daysUsed = 0;
  243. for (i = 0; i < ARRAY_SIZE(line->cl_Dow); ++i) {
  244. if (line->cl_Dow[i] == 0) {
  245. weekUsed = 1;
  246. break;
  247. }
  248. }
  249. for (i = 0; i < ARRAY_SIZE(line->cl_Days); ++i) {
  250. if (line->cl_Days[i] == 0) {
  251. daysUsed = 1;
  252. break;
  253. }
  254. }
  255. if (weekUsed != daysUsed) {
  256. if (weekUsed)
  257. memset(line->cl_Days, 0, sizeof(line->cl_Days));
  258. else /* daysUsed */
  259. memset(line->cl_Dow, 0, sizeof(line->cl_Dow));
  260. }
  261. }
  262. /*
  263. * delete_cronfile() - delete user database
  264. *
  265. * Note: multiple entries for same user may exist if we were unable to
  266. * completely delete a database due to running processes.
  267. */
  268. //FIXME: we will start a new job even if the old job is running
  269. //if crontab was reloaded: crond thinks that "new" job is different from "old"
  270. //even if they are in fact completely the same. Example
  271. //Crontab was:
  272. // 0-59 * * * * job1
  273. // 0-59 * * * * long_running_job2
  274. //User edits crontab to:
  275. // 0-59 * * * * job1_updated
  276. // 0-59 * * * * long_running_job2
  277. //Bug: crond can now start another long_running_job2 even if old one
  278. //is still running.
  279. //OTOH most other versions of cron do not wait for job termination anyway,
  280. //they end up with multiple copies of jobs if they don't terminate soon enough.
  281. static void delete_cronfile(const char *userName)
  282. {
  283. CronFile **pfile = &G.cron_files;
  284. CronFile *file;
  285. while ((file = *pfile) != NULL) {
  286. if (strcmp(userName, file->cf_username) == 0) {
  287. CronLine **pline = &file->cf_lines;
  288. CronLine *line;
  289. file->cf_has_running = 0;
  290. file->cf_deleted = 1;
  291. while ((line = *pline) != NULL) {
  292. if (line->cl_pid > 0) {
  293. file->cf_has_running = 1;
  294. pline = &line->cl_next;
  295. } else {
  296. *pline = line->cl_next;
  297. free(line->cl_cmd);
  298. free(line);
  299. }
  300. }
  301. if (file->cf_has_running == 0) {
  302. *pfile = file->cf_next;
  303. free(file->cf_username);
  304. free(file);
  305. continue;
  306. }
  307. }
  308. pfile = &file->cf_next;
  309. }
  310. }
  311. static void load_crontab(const char *fileName)
  312. {
  313. struct parser_t *parser;
  314. struct stat sbuf;
  315. int maxLines;
  316. char *tokens[6];
  317. #if ENABLE_FEATURE_CROND_CALL_SENDMAIL
  318. char *mailTo = NULL;
  319. #endif
  320. delete_cronfile(fileName);
  321. if (!getpwnam(fileName)) {
  322. crondlog(LVL7 "ignoring file '%s' (no such user)", fileName);
  323. return;
  324. }
  325. parser = config_open(fileName);
  326. if (!parser)
  327. return;
  328. maxLines = (strcmp(fileName, "root") == 0) ? 65535 : MAXLINES;
  329. if (fstat(fileno(parser->fp), &sbuf) == 0 && sbuf.st_uid == DAEMON_UID) {
  330. CronFile *file = xzalloc(sizeof(CronFile));
  331. CronLine **pline;
  332. int n;
  333. file->cf_username = xstrdup(fileName);
  334. pline = &file->cf_lines;
  335. while (1) {
  336. CronLine *line;
  337. if (!--maxLines)
  338. break;
  339. n = config_read(parser, tokens, 6, 1, "# \t", PARSE_NORMAL | PARSE_KEEP_COPY);
  340. if (!n)
  341. break;
  342. if (DebugOpt)
  343. crondlog(LVL5 "user:%s entry:%s", fileName, parser->data);
  344. /* check if line is setting MAILTO= */
  345. if (0 == strncmp(tokens[0], "MAILTO=", 7)) {
  346. #if ENABLE_FEATURE_CROND_CALL_SENDMAIL
  347. free(mailTo);
  348. mailTo = (tokens[0][7]) ? xstrdup(&tokens[0][7]) : NULL;
  349. #endif /* otherwise just ignore such lines */
  350. continue;
  351. }
  352. /* check if a minimum of tokens is specified */
  353. if (n < 6)
  354. continue;
  355. *pline = line = xzalloc(sizeof(*line));
  356. /* parse date ranges */
  357. ParseField(file->cf_username, line->cl_Mins, 60, 0, NULL, tokens[0]);
  358. ParseField(file->cf_username, line->cl_Hrs, 24, 0, NULL, tokens[1]);
  359. ParseField(file->cf_username, line->cl_Days, 32, 0, NULL, tokens[2]);
  360. ParseField(file->cf_username, line->cl_Mons, 12, -1, MonAry, tokens[3]);
  361. ParseField(file->cf_username, line->cl_Dow, 7, 0, DowAry, tokens[4]);
  362. /*
  363. * fix days and dow - if one is not "*" and the other
  364. * is "*", the other is set to 0, and vise-versa
  365. */
  366. FixDayDow(line);
  367. #if ENABLE_FEATURE_CROND_CALL_SENDMAIL
  368. /* copy mailto (can be NULL) */
  369. line->cl_mailto = xstrdup(mailTo);
  370. #endif
  371. /* copy command */
  372. line->cl_cmd = xstrdup(tokens[5]);
  373. if (DebugOpt) {
  374. crondlog(LVL5 " command:%s", tokens[5]);
  375. }
  376. pline = &line->cl_next;
  377. //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]);
  378. }
  379. *pline = NULL;
  380. file->cf_next = G.cron_files;
  381. G.cron_files = file;
  382. if (maxLines == 0) {
  383. crondlog(WARN9 "user %s: too many lines", fileName);
  384. }
  385. }
  386. config_close(parser);
  387. }
  388. static void process_cron_update_file(void)
  389. {
  390. FILE *fi;
  391. char buf[256];
  392. fi = fopen_for_read(CRONUPDATE);
  393. if (fi != NULL) {
  394. unlink(CRONUPDATE);
  395. while (fgets(buf, sizeof(buf), fi) != NULL) {
  396. /* use first word only */
  397. skip_non_whitespace(buf)[0] = '\0';
  398. load_crontab(buf);
  399. }
  400. fclose(fi);
  401. }
  402. }
  403. static void rescan_crontab_dir(void)
  404. {
  405. CronFile *file;
  406. /* Delete all files until we only have ones with running jobs (or none) */
  407. again:
  408. for (file = G.cron_files; file; file = file->cf_next) {
  409. if (!file->cf_deleted) {
  410. delete_cronfile(file->cf_username);
  411. goto again;
  412. }
  413. }
  414. /* Remove cron update file */
  415. unlink(CRONUPDATE);
  416. /* Re-chdir, in case directory was renamed & deleted */
  417. if (chdir(G.crontab_dir_name) < 0) {
  418. crondlog(DIE9 "chdir(%s)", G.crontab_dir_name);
  419. }
  420. /* Scan directory and add associated users */
  421. {
  422. DIR *dir = opendir(".");
  423. struct dirent *den;
  424. if (!dir)
  425. crondlog(DIE9 "chdir(%s)", "."); /* exits */
  426. while ((den = readdir(dir)) != NULL) {
  427. if (strchr(den->d_name, '.') != NULL) {
  428. continue;
  429. }
  430. load_crontab(den->d_name);
  431. }
  432. closedir(dir);
  433. }
  434. }
  435. #if SETENV_LEAKS
  436. /* We set environment *before* vfork (because we want to use vfork),
  437. * so we cannot use setenv() - repeated calls to setenv() may leak memory!
  438. * Using putenv(), and freeing memory after unsetenv() won't leak */
  439. static void safe_setenv(char **pvar_val, const char *var, const char *val)
  440. {
  441. char *var_val = *pvar_val;
  442. if (var_val) {
  443. bb_unsetenv_and_free(var_val);
  444. }
  445. *pvar_val = xasprintf("%s=%s", var, val);
  446. putenv(*pvar_val);
  447. }
  448. #endif
  449. static void set_env_vars(struct passwd *pas)
  450. {
  451. #if SETENV_LEAKS
  452. safe_setenv(&G.env_var_user, "USER", pas->pw_name);
  453. safe_setenv(&G.env_var_home, "HOME", pas->pw_dir);
  454. /* if we want to set user's shell instead: */
  455. /*safe_setenv(G.env_var_shell, "SHELL", pas->pw_shell);*/
  456. #else
  457. xsetenv("USER", pas->pw_name);
  458. xsetenv("HOME", pas->pw_dir);
  459. #endif
  460. /* currently, we use constant one: */
  461. /*setenv("SHELL", DEFAULT_SHELL, 1); - done earlier */
  462. }
  463. static void change_user(struct passwd *pas)
  464. {
  465. /* careful: we're after vfork! */
  466. change_identity(pas); /* - initgroups, setgid, setuid */
  467. if (chdir(pas->pw_dir) < 0) {
  468. crondlog(WARN9 "chdir(%s)", pas->pw_dir);
  469. if (chdir(TMPDIR) < 0) {
  470. crondlog(DIE9 "chdir(%s)", TMPDIR); /* exits */
  471. }
  472. }
  473. }
  474. // TODO: sendmail should be _run-time_ option, not compile-time!
  475. #if ENABLE_FEATURE_CROND_CALL_SENDMAIL
  476. static pid_t
  477. fork_job(const char *user, int mailFd,
  478. const char *prog,
  479. const char *shell_cmd /* if NULL, we run sendmail */
  480. ) {
  481. struct passwd *pas;
  482. pid_t pid;
  483. /* prepare things before vfork */
  484. pas = getpwnam(user);
  485. if (!pas) {
  486. crondlog(WARN9 "can't get uid for %s", user);
  487. goto err;
  488. }
  489. set_env_vars(pas);
  490. pid = vfork();
  491. if (pid == 0) {
  492. /* CHILD */
  493. /* initgroups, setgid, setuid, and chdir to home or TMPDIR */
  494. change_user(pas);
  495. if (DebugOpt) {
  496. crondlog(LVL5 "child running %s", prog);
  497. }
  498. if (mailFd >= 0) {
  499. xmove_fd(mailFd, shell_cmd ? 1 : 0);
  500. dup2(1, 2);
  501. }
  502. /* crond 3.0pl1-100 puts tasks in separate process groups */
  503. bb_setpgrp();
  504. execlp(prog, prog, (shell_cmd ? "-c" : SENDMAIL_ARGS), shell_cmd, (char *) NULL);
  505. crondlog(ERR20 "can't execute '%s' for user %s", prog, user);
  506. if (shell_cmd) {
  507. fdprintf(1, "Exec failed: %s -c %s\n", prog, shell_cmd);
  508. }
  509. _exit(EXIT_SUCCESS);
  510. }
  511. if (pid < 0) {
  512. /* FORK FAILED */
  513. crondlog(ERR20 "can't vfork");
  514. err:
  515. pid = 0;
  516. } /* else: PARENT, FORK SUCCESS */
  517. /*
  518. * Close the mail file descriptor.. we can't just leave it open in
  519. * a structure, closing it later, because we might run out of descriptors
  520. */
  521. if (mailFd >= 0) {
  522. close(mailFd);
  523. }
  524. return pid;
  525. }
  526. static void start_one_job(const char *user, CronLine *line)
  527. {
  528. char mailFile[128];
  529. int mailFd = -1;
  530. line->cl_pid = 0;
  531. line->cl_empty_mail_size = 0;
  532. if (line->cl_mailto) {
  533. /* Open mail file (owner is root so nobody can screw with it) */
  534. snprintf(mailFile, sizeof(mailFile), "%s/cron.%s.%d", TMPDIR, user, getpid());
  535. mailFd = open(mailFile, O_CREAT | O_TRUNC | O_WRONLY | O_EXCL | O_APPEND, 0600);
  536. if (mailFd >= 0) {
  537. fdprintf(mailFd, "To: %s\nSubject: cron: %s\n\n", line->cl_mailto,
  538. line->cl_cmd);
  539. line->cl_empty_mail_size = lseek(mailFd, 0, SEEK_CUR);
  540. } else {
  541. crondlog(ERR20 "can't create mail file %s for user %s, "
  542. "discarding output", mailFile, user);
  543. }
  544. }
  545. line->cl_pid = fork_job(user, mailFd, DEFAULT_SHELL, line->cl_cmd);
  546. if (mailFd >= 0) {
  547. if (line->cl_pid <= 0) {
  548. unlink(mailFile);
  549. } else {
  550. /* rename mail-file based on pid of process */
  551. char *mailFile2 = xasprintf("%s/cron.%s.%d", TMPDIR, user, (int)line->cl_pid);
  552. rename(mailFile, mailFile2); // TODO: xrename?
  553. free(mailFile2);
  554. }
  555. }
  556. }
  557. /*
  558. * process_finished_job - called when job terminates and when mail terminates
  559. */
  560. static void process_finished_job(const char *user, CronLine *line)
  561. {
  562. pid_t pid;
  563. int mailFd;
  564. char mailFile[128];
  565. struct stat sbuf;
  566. pid = line->cl_pid;
  567. line->cl_pid = 0;
  568. if (pid <= 0) {
  569. /* No job */
  570. return;
  571. }
  572. if (line->cl_empty_mail_size <= 0) {
  573. /* End of job and no mail file, or end of sendmail job */
  574. return;
  575. }
  576. /*
  577. * End of primary job - check for mail file.
  578. * If size has changed and the file is still valid, we send it.
  579. */
  580. snprintf(mailFile, sizeof(mailFile), "%s/cron.%s.%d", TMPDIR, user, (int)pid);
  581. mailFd = open(mailFile, O_RDONLY);
  582. unlink(mailFile);
  583. if (mailFd < 0) {
  584. return;
  585. }
  586. if (fstat(mailFd, &sbuf) < 0
  587. || sbuf.st_uid != DAEMON_UID
  588. || sbuf.st_nlink != 0
  589. || sbuf.st_size == line->cl_empty_mail_size
  590. || !S_ISREG(sbuf.st_mode)
  591. ) {
  592. close(mailFd);
  593. return;
  594. }
  595. line->cl_empty_mail_size = 0;
  596. /* if (line->cl_mailto) - always true if cl_empty_mail_size was nonzero */
  597. line->cl_pid = fork_job(user, mailFd, SENDMAIL, NULL);
  598. }
  599. #else /* !ENABLE_FEATURE_CROND_CALL_SENDMAIL */
  600. static void start_one_job(const char *user, CronLine *line)
  601. {
  602. struct passwd *pas;
  603. pid_t pid;
  604. pas = getpwnam(user);
  605. if (!pas) {
  606. crondlog(WARN9 "can't get uid for %s", user);
  607. goto err;
  608. }
  609. /* Prepare things before vfork */
  610. set_env_vars(pas);
  611. /* Fork as the user in question and run program */
  612. pid = vfork();
  613. if (pid == 0) {
  614. /* CHILD */
  615. /* initgroups, setgid, setuid, and chdir to home or TMPDIR */
  616. change_user(pas);
  617. if (DebugOpt) {
  618. crondlog(LVL5 "child running %s", DEFAULT_SHELL);
  619. }
  620. /* crond 3.0pl1-100 puts tasks in separate process groups */
  621. bb_setpgrp();
  622. execl(DEFAULT_SHELL, DEFAULT_SHELL, "-c", line->cl_cmd, (char *) NULL);
  623. crondlog(ERR20 "can't execute '%s' for user %s", DEFAULT_SHELL, user);
  624. _exit(EXIT_SUCCESS);
  625. }
  626. if (pid < 0) {
  627. /* FORK FAILED */
  628. crondlog(ERR20 "can't vfork");
  629. err:
  630. pid = 0;
  631. }
  632. line->cl_pid = pid;
  633. }
  634. #define process_finished_job(user, line) ((line)->cl_pid = 0)
  635. #endif /* !ENABLE_FEATURE_CROND_CALL_SENDMAIL */
  636. /*
  637. * Determine which jobs need to be run. Under normal conditions, the
  638. * period is about a minute (one scan). Worst case it will be one
  639. * hour (60 scans).
  640. */
  641. static void flag_starting_jobs(time_t t1, time_t t2)
  642. {
  643. time_t t;
  644. /* Find jobs > t1 and <= t2 */
  645. for (t = t1 - t1 % 60; t <= t2; t += 60) {
  646. struct tm *ptm;
  647. CronFile *file;
  648. CronLine *line;
  649. if (t <= t1)
  650. continue;
  651. ptm = localtime(&t);
  652. for (file = G.cron_files; file; file = file->cf_next) {
  653. if (DebugOpt)
  654. crondlog(LVL5 "file %s:", file->cf_username);
  655. if (file->cf_deleted)
  656. continue;
  657. for (line = file->cf_lines; line; line = line->cl_next) {
  658. if (DebugOpt)
  659. crondlog(LVL5 " line %s", line->cl_cmd);
  660. if (line->cl_Mins[ptm->tm_min]
  661. && line->cl_Hrs[ptm->tm_hour]
  662. && (line->cl_Days[ptm->tm_mday] || line->cl_Dow[ptm->tm_wday])
  663. && line->cl_Mons[ptm->tm_mon]
  664. ) {
  665. if (DebugOpt) {
  666. crondlog(LVL5 " job: %d %s",
  667. (int)line->cl_pid, line->cl_cmd);
  668. }
  669. if (line->cl_pid > 0) {
  670. crondlog(LVL8 "user %s: process already running: %s",
  671. file->cf_username, line->cl_cmd);
  672. } else if (line->cl_pid == 0) {
  673. line->cl_pid = -1;
  674. file->cf_wants_starting = 1;
  675. }
  676. }
  677. }
  678. }
  679. }
  680. }
  681. static void start_jobs(void)
  682. {
  683. CronFile *file;
  684. CronLine *line;
  685. for (file = G.cron_files; file; file = file->cf_next) {
  686. if (!file->cf_wants_starting)
  687. continue;
  688. file->cf_wants_starting = 0;
  689. for (line = file->cf_lines; line; line = line->cl_next) {
  690. pid_t pid;
  691. if (line->cl_pid >= 0)
  692. continue;
  693. start_one_job(file->cf_username, line);
  694. pid = line->cl_pid;
  695. crondlog(LVL8 "USER %s pid %3d cmd %s",
  696. file->cf_username, (int)pid, line->cl_cmd);
  697. if (pid < 0) {
  698. file->cf_wants_starting = 1;
  699. }
  700. if (pid > 0) {
  701. file->cf_has_running = 1;
  702. }
  703. }
  704. }
  705. }
  706. /*
  707. * Check for job completion, return number of jobs still running after
  708. * all done.
  709. */
  710. static int check_completions(void)
  711. {
  712. CronFile *file;
  713. CronLine *line;
  714. int num_still_running = 0;
  715. for (file = G.cron_files; file; file = file->cf_next) {
  716. if (!file->cf_has_running)
  717. continue;
  718. file->cf_has_running = 0;
  719. for (line = file->cf_lines; line; line = line->cl_next) {
  720. int r;
  721. if (line->cl_pid <= 0)
  722. continue;
  723. r = waitpid(line->cl_pid, NULL, WNOHANG);
  724. if (r < 0 || r == line->cl_pid) {
  725. process_finished_job(file->cf_username, line);
  726. if (line->cl_pid == 0) {
  727. /* sendmail was not started for it */
  728. continue;
  729. }
  730. /* else: sendmail was started, job is still running, fall thru */
  731. }
  732. /* else: r == 0: "process is still running" */
  733. file->cf_has_running = 1;
  734. }
  735. //FIXME: if !file->cf_has_running && file->deleted: delete it!
  736. //otherwise deleted entries will stay forever, right?
  737. num_still_running += file->cf_has_running;
  738. }
  739. return num_still_running;
  740. }
  741. int crond_main(int argc, char **argv) MAIN_EXTERNALLY_VISIBLE;
  742. int crond_main(int argc UNUSED_PARAM, char **argv)
  743. {
  744. time_t t2;
  745. int rescan;
  746. int sleep_time;
  747. unsigned opts;
  748. INIT_G();
  749. /* "-b after -f is ignored", and so on for every pair a-b */
  750. opt_complementary = "f-b:b-f:S-L:L-S" IF_FEATURE_CROND_D(":d-l")
  751. ":l+:d+"; /* -l and -d have numeric param */
  752. opts = getopt32(argv, "l:L:fbSc:" IF_FEATURE_CROND_D("d:"),
  753. &G.log_level, &G.log_filename, &G.crontab_dir_name
  754. IF_FEATURE_CROND_D(,&G.log_level));
  755. /* both -d N and -l N set the same variable: G.log_level */
  756. if (!(opts & OPT_f)) {
  757. /* close stdin, stdout, stderr.
  758. * close unused descriptors - don't need them. */
  759. bb_daemonize_or_rexec(DAEMON_CLOSE_EXTRA_FDS, argv);
  760. }
  761. if (!(opts & OPT_d) && G.log_filename == NULL) {
  762. /* logging to syslog */
  763. openlog(applet_name, LOG_CONS | LOG_PID, LOG_CRON);
  764. logmode = LOGMODE_SYSLOG;
  765. }
  766. xchdir(G.crontab_dir_name);
  767. //signal(SIGHUP, SIG_IGN); /* ? original crond dies on HUP... */
  768. xsetenv("SHELL", DEFAULT_SHELL); /* once, for all future children */
  769. crondlog(LVL8 "crond (busybox "BB_VER") started, log level %d", G.log_level);
  770. rescan_crontab_dir();
  771. write_pidfile("/var/run/crond.pid");
  772. /* Main loop */
  773. t2 = time(NULL);
  774. rescan = 60;
  775. sleep_time = 60;
  776. for (;;) {
  777. struct stat sbuf;
  778. time_t t1;
  779. long dt;
  780. t1 = t2;
  781. /* Synchronize to 1 minute, minimum 1 second */
  782. sleep(sleep_time - (time(NULL) % sleep_time) + 1);
  783. t2 = time(NULL);
  784. dt = (long)t2 - (long)t1;
  785. /*
  786. * The file 'cron.update' is checked to determine new cron
  787. * jobs. The directory is rescanned once an hour to deal
  788. * with any screwups.
  789. *
  790. * Check for time jump. Disparities over an hour either way
  791. * result in resynchronization. A negative disparity
  792. * less than an hour causes us to effectively sleep until we
  793. * match the original time (i.e. no re-execution of jobs that
  794. * have just been run). A positive disparity less than
  795. * an hour causes intermediate jobs to be run, but only once
  796. * in the worst case.
  797. *
  798. * When running jobs, the inequality used is greater but not
  799. * equal to t1, and less then or equal to t2.
  800. */
  801. if (stat(G.crontab_dir_name, &sbuf) != 0)
  802. sbuf.st_mtime = 0; /* force update (once) if dir was deleted */
  803. if (G.crontab_dir_mtime != sbuf.st_mtime) {
  804. G.crontab_dir_mtime = sbuf.st_mtime;
  805. rescan = 1;
  806. }
  807. if (--rescan == 0) {
  808. rescan = 60;
  809. rescan_crontab_dir();
  810. }
  811. process_cron_update_file();
  812. if (DebugOpt)
  813. crondlog(LVL5 "wakeup dt=%ld", dt);
  814. if (dt < -60 * 60 || dt > 60 * 60) {
  815. crondlog(WARN9 "time disparity of %ld minutes detected", dt / 60);
  816. /* and we do not run any jobs in this case */
  817. } else if (dt > 0) {
  818. /* Usual case: time advances forward, as expected */
  819. flag_starting_jobs(t1, t2);
  820. start_jobs();
  821. if (check_completions() > 0) {
  822. /* some jobs are still running */
  823. sleep_time = 10;
  824. } else {
  825. sleep_time = 60;
  826. }
  827. }
  828. /* else: time jumped back, do not run any jobs */
  829. } /* for (;;) */
  830. return 0; /* not reached */
  831. }