3
0

crond.c 23 KB

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