crond.c 20 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985
  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. * Vladimir Oleynik <dzo@simtreas.ru> (C) 2002
  9. *
  10. * Licensed under the GPL v2 or later, see the file LICENSE in this tarball.
  11. */
  12. #define VERSION "2.3.2"
  13. #include "libbb.h"
  14. #include <sys/syslog.h>
  15. #define arysize(ary) (sizeof(ary)/sizeof((ary)[0]))
  16. #ifndef CRONTABS
  17. #define CRONTABS "/var/spool/cron/crontabs"
  18. #endif
  19. #ifndef TMPDIR
  20. #define TMPDIR "/var/spool/cron"
  21. #endif
  22. #ifndef SENDMAIL
  23. #define SENDMAIL "/usr/sbin/sendmail"
  24. #endif
  25. #ifndef SENDMAIL_ARGS
  26. #define SENDMAIL_ARGS "-ti", "oem"
  27. #endif
  28. #ifndef CRONUPDATE
  29. #define CRONUPDATE "cron.update"
  30. #endif
  31. #ifndef MAXLINES
  32. #define MAXLINES 256 /* max lines in non-root crontabs */
  33. #endif
  34. typedef struct CronFile {
  35. struct CronFile *cf_Next;
  36. struct CronLine *cf_LineBase;
  37. char *cf_User; /* username */
  38. int cf_Ready; /* bool: one or more jobs ready */
  39. int cf_Running; /* bool: one or more jobs running */
  40. int cf_Deleted; /* marked for deletion, ignore */
  41. } CronFile;
  42. typedef struct CronLine {
  43. struct CronLine *cl_Next;
  44. char *cl_Shell; /* shell command */
  45. pid_t cl_Pid; /* running pid, 0, or armed (-1) */
  46. int cl_MailFlag; /* running pid is for mail */
  47. int cl_MailPos; /* 'empty file' size */
  48. char cl_Mins[60]; /* 0-59 */
  49. char cl_Hrs[24]; /* 0-23 */
  50. char cl_Days[32]; /* 1-31 */
  51. char cl_Mons[12]; /* 0-11 */
  52. char cl_Dow[7]; /* 0-6, beginning sunday */
  53. } CronLine;
  54. #define RUN_RANOUT 1
  55. #define RUN_RUNNING 2
  56. #define RUN_FAILED 3
  57. #define DaemonUid 0
  58. #if ENABLE_DEBUG_CROND_OPTION
  59. static unsigned DebugOpt;
  60. #endif
  61. static unsigned LogLevel = 8;
  62. static const char *LogFile;
  63. static const char *CDir = CRONTABS;
  64. static void startlogger(void);
  65. static void CheckUpdates(void);
  66. static void SynchronizeDir(void);
  67. static int TestJobs(time_t t1, time_t t2);
  68. static void RunJobs(void);
  69. static int CheckJobs(void);
  70. static void RunJob(const char *user, CronLine * line);
  71. #if ENABLE_FEATURE_CROND_CALL_SENDMAIL
  72. static void EndJob(const char *user, CronLine * line);
  73. #else
  74. #define EndJob(user, line) line->cl_Pid = 0
  75. #endif
  76. static void DeleteFile(const char *userName);
  77. static CronFile *FileBase;
  78. static void crondlog(const char *ctl, ...)
  79. {
  80. va_list va;
  81. const char *fmt;
  82. int level = (int) (ctl[0] & 0xf);
  83. int type = level == 20 ?
  84. LOG_ERR : ((ctl[0] & 0100) ? LOG_WARNING : LOG_NOTICE);
  85. va_start(va, ctl);
  86. fmt = ctl + 1;
  87. if (level >= LogLevel) {
  88. #if ENABLE_DEBUG_CROND_OPTION
  89. if (DebugOpt) {
  90. vfprintf(stderr, fmt, va);
  91. } else
  92. #endif
  93. if (LogFile == 0) {
  94. vsyslog(type, fmt, va);
  95. } else {
  96. #if !ENABLE_DEBUG_CROND_OPTION
  97. int logfd = open(LogFile, O_WRONLY | O_CREAT | O_APPEND, 0600);
  98. #else
  99. int logfd = open3_or_warn(LogFile, O_WRONLY | O_CREAT | O_APPEND, 0600);
  100. #endif
  101. if (logfd >= 0) {
  102. vdprintf(logfd, fmt, va);
  103. close(logfd);
  104. }
  105. }
  106. }
  107. va_end(va);
  108. if (ctl[0] & 0200) {
  109. exit(20);
  110. }
  111. }
  112. int crond_main(int ac, char **av);
  113. int crond_main(int ac, char **av)
  114. {
  115. unsigned opt;
  116. char *lopt, *Lopt, *copt;
  117. USE_DEBUG_CROND_OPTION(char *dopt;)
  118. opt_complementary = "f-b:b-f:S-L:L-S" USE_DEBUG_CROND_OPTION(":d-l");
  119. opterr = 0; /* disable getopt 'errors' message. */
  120. opt = getopt32(ac, av, "l:L:fbSc:" USE_DEBUG_CROND_OPTION("d:"),
  121. &lopt, &Lopt, &copt USE_DEBUG_CROND_OPTION(, &dopt));
  122. if (opt & 1) /* -l */
  123. LogLevel = xatou(lopt);
  124. if (opt & 2) /* -L */
  125. if (*Lopt)
  126. LogFile = Lopt;
  127. if (opt & 32) /* -c */
  128. if (*copt)
  129. CDir = copt;
  130. #if ENABLE_DEBUG_CROND_OPTION
  131. if (opt & 64) { /* -d */
  132. DebugOpt = xatou(dopt);
  133. LogLevel = 0;
  134. }
  135. #endif
  136. /* close stdin and stdout, stderr.
  137. * close unused descriptors - don't need.
  138. * optional detach from controlling terminal
  139. */
  140. if (!(opt & 4))
  141. bb_daemonize_or_rexec(DAEMON_CLOSE_EXTRA_FDS, av);
  142. xchdir(CDir);
  143. signal(SIGHUP, SIG_IGN); /* ? original crond dies on HUP... */
  144. startlogger(); /* need if syslog mode selected */
  145. /*
  146. * main loop - synchronize to 1 second after the minute, minimum sleep
  147. * of 1 second.
  148. */
  149. crondlog("\011%s " VERSION " dillon, started, log level %d\n",
  150. applet_name, LogLevel);
  151. SynchronizeDir();
  152. {
  153. time_t t1 = time(NULL);
  154. time_t t2;
  155. long dt;
  156. int rescan = 60;
  157. short sleep_time = 60;
  158. write_pidfile("/var/run/crond.pid");
  159. for (;;) {
  160. sleep((sleep_time + 1) - (short) (time(NULL) % sleep_time));
  161. t2 = time(NULL);
  162. dt = t2 - t1;
  163. /*
  164. * The file 'cron.update' is checked to determine new cron
  165. * jobs. The directory is rescanned once an hour to deal
  166. * with any screwups.
  167. *
  168. * check for disparity. Disparities over an hour either way
  169. * result in resynchronization. A reverse-indexed disparity
  170. * less then an hour causes us to effectively sleep until we
  171. * match the original time (i.e. no re-execution of jobs that
  172. * have just been run). A forward-indexed disparity less then
  173. * an hour causes intermediate jobs to be run, but only once
  174. * in the worst case.
  175. *
  176. * when running jobs, the inequality used is greater but not
  177. * equal to t1, and less then or equal to t2.
  178. */
  179. if (--rescan == 0) {
  180. rescan = 60;
  181. SynchronizeDir();
  182. }
  183. CheckUpdates();
  184. #if ENABLE_DEBUG_CROND_OPTION
  185. if (DebugOpt)
  186. crondlog("\005Wakeup dt=%d\n", dt);
  187. #endif
  188. if (dt < -60 * 60 || dt > 60 * 60) {
  189. t1 = t2;
  190. crondlog("\111time disparity of %d minutes detected\n", dt / 60);
  191. } else if (dt > 0) {
  192. TestJobs(t1, t2);
  193. RunJobs();
  194. sleep(5);
  195. if (CheckJobs() > 0) {
  196. sleep_time = 10;
  197. } else {
  198. sleep_time = 60;
  199. }
  200. t1 = t2;
  201. }
  202. }
  203. }
  204. return 0; /* not reached */
  205. }
  206. static int ChangeUser(const char *user)
  207. {
  208. struct passwd *pas;
  209. const char *err_msg;
  210. /*
  211. * Obtain password entry and change privileges
  212. */
  213. pas = getpwnam(user);
  214. if (pas == 0) {
  215. crondlog("\011failed to get uid for %s", user);
  216. return -1;
  217. }
  218. setenv("USER", pas->pw_name, 1);
  219. setenv("HOME", pas->pw_dir, 1);
  220. setenv("SHELL", DEFAULT_SHELL, 1);
  221. /*
  222. * Change running state to the user in question
  223. */
  224. err_msg = change_identity_e2str(pas);
  225. if (err_msg) {
  226. crondlog("\011%s for user %s", err_msg, user);
  227. return -1;
  228. }
  229. if (chdir(pas->pw_dir) < 0) {
  230. crondlog("\011chdir failed: %s: %m", pas->pw_dir);
  231. if (chdir(TMPDIR) < 0) {
  232. crondlog("\011chdir failed: %s: %m", TMPDIR);
  233. return -1;
  234. }
  235. }
  236. return pas->pw_uid;
  237. }
  238. static void startlogger(void)
  239. {
  240. if (LogFile == 0) {
  241. openlog(applet_name, LOG_CONS | LOG_PID, LOG_CRON);
  242. }
  243. #if ENABLE_DEBUG_CROND_OPTION
  244. else { /* test logfile */
  245. int logfd;
  246. logfd = open3_or_warn(LogFile, O_WRONLY | O_CREAT | O_APPEND, 0600);
  247. if (logfd >= 0) {
  248. close(logfd);
  249. }
  250. }
  251. #endif
  252. }
  253. static const char *const DowAry[] = {
  254. "sun",
  255. "mon",
  256. "tue",
  257. "wed",
  258. "thu",
  259. "fri",
  260. "sat",
  261. "Sun",
  262. "Mon",
  263. "Tue",
  264. "Wed",
  265. "Thu",
  266. "Fri",
  267. "Sat",
  268. NULL
  269. };
  270. static const char *const MonAry[] = {
  271. "jan",
  272. "feb",
  273. "mar",
  274. "apr",
  275. "may",
  276. "jun",
  277. "jul",
  278. "aug",
  279. "sep",
  280. "oct",
  281. "nov",
  282. "dec",
  283. "Jan",
  284. "Feb",
  285. "Mar",
  286. "Apr",
  287. "May",
  288. "Jun",
  289. "Jul",
  290. "Aug",
  291. "Sep",
  292. "Oct",
  293. "Nov",
  294. "Dec",
  295. NULL
  296. };
  297. static char *ParseField(char *user, char *ary, int modvalue, int off,
  298. const char *const *names, char *ptr)
  299. {
  300. char *base = ptr;
  301. int n1 = -1;
  302. int n2 = -1;
  303. if (base == NULL) {
  304. return NULL;
  305. }
  306. while (*ptr != ' ' && *ptr != '\t' && *ptr != '\n') {
  307. int skip = 0;
  308. /* Handle numeric digit or symbol or '*' */
  309. if (*ptr == '*') {
  310. n1 = 0; /* everything will be filled */
  311. n2 = modvalue - 1;
  312. skip = 1;
  313. ++ptr;
  314. } else if (*ptr >= '0' && *ptr <= '9') {
  315. if (n1 < 0) {
  316. n1 = strtol(ptr, &ptr, 10) + off;
  317. } else {
  318. n2 = strtol(ptr, &ptr, 10) + off;
  319. }
  320. skip = 1;
  321. } else if (names) {
  322. int i;
  323. for (i = 0; names[i]; ++i) {
  324. if (strncmp(ptr, names[i], strlen(names[i])) == 0) {
  325. break;
  326. }
  327. }
  328. if (names[i]) {
  329. ptr += strlen(names[i]);
  330. if (n1 < 0) {
  331. n1 = i;
  332. } else {
  333. n2 = i;
  334. }
  335. skip = 1;
  336. }
  337. }
  338. /* handle optional range '-' */
  339. if (skip == 0) {
  340. crondlog("\111failed user %s parsing %s\n", user, base);
  341. return NULL;
  342. }
  343. if (*ptr == '-' && n2 < 0) {
  344. ++ptr;
  345. continue;
  346. }
  347. /*
  348. * collapse single-value ranges, handle skipmark, and fill
  349. * in the character array appropriately.
  350. */
  351. if (n2 < 0) {
  352. n2 = n1;
  353. }
  354. if (*ptr == '/') {
  355. skip = strtol(ptr + 1, &ptr, 10);
  356. }
  357. /*
  358. * fill array, using a failsafe is the easiest way to prevent
  359. * an endless loop
  360. */
  361. {
  362. int s0 = 1;
  363. int failsafe = 1024;
  364. --n1;
  365. do {
  366. n1 = (n1 + 1) % modvalue;
  367. if (--s0 == 0) {
  368. ary[n1 % modvalue] = 1;
  369. s0 = skip;
  370. }
  371. }
  372. while (n1 != n2 && --failsafe);
  373. if (failsafe == 0) {
  374. crondlog("\111failed user %s parsing %s\n", user, base);
  375. return NULL;
  376. }
  377. }
  378. if (*ptr != ',') {
  379. break;
  380. }
  381. ++ptr;
  382. n1 = -1;
  383. n2 = -1;
  384. }
  385. if (*ptr != ' ' && *ptr != '\t' && *ptr != '\n') {
  386. crondlog("\111failed user %s parsing %s\n", user, base);
  387. return NULL;
  388. }
  389. while (*ptr == ' ' || *ptr == '\t' || *ptr == '\n') {
  390. ++ptr;
  391. }
  392. #if ENABLE_DEBUG_CROND_OPTION
  393. if (DebugOpt) {
  394. int i;
  395. for (i = 0; i < modvalue; ++i) {
  396. crondlog("\005%d", ary[i]);
  397. }
  398. crondlog("\005\n");
  399. }
  400. #endif
  401. return ptr;
  402. }
  403. static void FixDayDow(CronLine * line)
  404. {
  405. int i;
  406. int weekUsed = 0;
  407. int daysUsed = 0;
  408. for (i = 0; i < (int)(arysize(line->cl_Dow)); ++i) {
  409. if (line->cl_Dow[i] == 0) {
  410. weekUsed = 1;
  411. break;
  412. }
  413. }
  414. for (i = 0; i < (int)(arysize(line->cl_Days)); ++i) {
  415. if (line->cl_Days[i] == 0) {
  416. daysUsed = 1;
  417. break;
  418. }
  419. }
  420. if (weekUsed && !daysUsed) {
  421. memset(line->cl_Days, 0, sizeof(line->cl_Days));
  422. }
  423. if (daysUsed && !weekUsed) {
  424. memset(line->cl_Dow, 0, sizeof(line->cl_Dow));
  425. }
  426. }
  427. static void SynchronizeFile(const char *fileName)
  428. {
  429. int maxEntries = MAXLINES;
  430. int maxLines;
  431. char buf[1024];
  432. if (strcmp(fileName, "root") == 0) {
  433. maxEntries = 65535;
  434. }
  435. maxLines = maxEntries * 10;
  436. if (fileName) {
  437. FILE *fi;
  438. DeleteFile(fileName);
  439. fi = fopen(fileName, "r");
  440. if (fi != NULL) {
  441. struct stat sbuf;
  442. if (fstat(fileno(fi), &sbuf) == 0 && sbuf.st_uid == DaemonUid) {
  443. CronFile *file = xzalloc(sizeof(CronFile));
  444. CronLine **pline;
  445. file->cf_User = strdup(fileName);
  446. pline = &file->cf_LineBase;
  447. while (fgets(buf, sizeof(buf), fi) != NULL && --maxLines) {
  448. CronLine line;
  449. char *ptr;
  450. trim(buf);
  451. if (buf[0] == 0 || buf[0] == '#') {
  452. continue;
  453. }
  454. if (--maxEntries == 0) {
  455. break;
  456. }
  457. memset(&line, 0, sizeof(line));
  458. #if ENABLE_DEBUG_CROND_OPTION
  459. if (DebugOpt) {
  460. crondlog("\111User %s Entry %s\n", fileName, buf);
  461. }
  462. #endif
  463. /* parse date ranges */
  464. ptr = ParseField(file->cf_User, line.cl_Mins, 60, 0, NULL, buf);
  465. ptr = ParseField(file->cf_User, line.cl_Hrs, 24, 0, NULL, ptr);
  466. ptr = ParseField(file->cf_User, line.cl_Days, 32, 0, NULL, ptr);
  467. ptr = ParseField(file->cf_User, line.cl_Mons, 12, -1, MonAry, ptr);
  468. ptr = ParseField(file->cf_User, line.cl_Dow, 7, 0, DowAry, ptr);
  469. /* check failure */
  470. if (ptr == NULL) {
  471. continue;
  472. }
  473. /*
  474. * fix days and dow - if one is not * and the other
  475. * is *, the other is set to 0, and vise-versa
  476. */
  477. FixDayDow(&line);
  478. *pline = xzalloc(sizeof(CronLine));
  479. **pline = line;
  480. /* copy command */
  481. (*pline)->cl_Shell = strdup(ptr);
  482. #if ENABLE_DEBUG_CROND_OPTION
  483. if (DebugOpt) {
  484. crondlog("\111 Command %s\n", ptr);
  485. }
  486. #endif
  487. pline = &((*pline)->cl_Next);
  488. }
  489. *pline = NULL;
  490. file->cf_Next = FileBase;
  491. FileBase = file;
  492. if (maxLines == 0 || maxEntries == 0) {
  493. crondlog("\111Maximum number of lines reached for user %s\n", fileName);
  494. }
  495. }
  496. fclose(fi);
  497. }
  498. }
  499. }
  500. static void CheckUpdates(void)
  501. {
  502. FILE *fi;
  503. char buf[256];
  504. fi = fopen(CRONUPDATE, "r");
  505. if (fi != NULL) {
  506. remove(CRONUPDATE);
  507. while (fgets(buf, sizeof(buf), fi) != NULL) {
  508. SynchronizeFile(strtok(buf, " \t\r\n"));
  509. }
  510. fclose(fi);
  511. }
  512. }
  513. static void SynchronizeDir(void)
  514. {
  515. /* Attempt to delete the database. */
  516. for (;;) {
  517. CronFile *file;
  518. for (file = FileBase; file && file->cf_Deleted; file = file->cf_Next);
  519. if (file == NULL) {
  520. break;
  521. }
  522. DeleteFile(file->cf_User);
  523. }
  524. /*
  525. * Remove cron update file
  526. *
  527. * Re-chdir, in case directory was renamed & deleted, or otherwise
  528. * screwed up.
  529. *
  530. * scan directory and add associated users
  531. */
  532. remove(CRONUPDATE);
  533. if (chdir(CDir) < 0) {
  534. crondlog("\311cannot find %s\n", CDir);
  535. }
  536. {
  537. DIR *dir = opendir(".");
  538. struct dirent *den;
  539. if (dir) {
  540. while ((den = readdir(dir))) {
  541. if (strchr(den->d_name, '.') != NULL) {
  542. continue;
  543. }
  544. if (getpwnam(den->d_name)) {
  545. SynchronizeFile(den->d_name);
  546. } else {
  547. crondlog("\007ignoring %s\n", den->d_name);
  548. }
  549. }
  550. closedir(dir);
  551. } else {
  552. crondlog("\311cannot open current dir!\n");
  553. }
  554. }
  555. }
  556. /*
  557. * DeleteFile() - delete user database
  558. *
  559. * Note: multiple entries for same user may exist if we were unable to
  560. * completely delete a database due to running processes.
  561. */
  562. static void DeleteFile(const char *userName)
  563. {
  564. CronFile **pfile = &FileBase;
  565. CronFile *file;
  566. while ((file = *pfile) != NULL) {
  567. if (strcmp(userName, file->cf_User) == 0) {
  568. CronLine **pline = &file->cf_LineBase;
  569. CronLine *line;
  570. file->cf_Running = 0;
  571. file->cf_Deleted = 1;
  572. while ((line = *pline) != NULL) {
  573. if (line->cl_Pid > 0) {
  574. file->cf_Running = 1;
  575. pline = &line->cl_Next;
  576. } else {
  577. *pline = line->cl_Next;
  578. free(line->cl_Shell);
  579. free(line);
  580. }
  581. }
  582. if (file->cf_Running == 0) {
  583. *pfile = file->cf_Next;
  584. free(file->cf_User);
  585. free(file);
  586. } else {
  587. pfile = &file->cf_Next;
  588. }
  589. } else {
  590. pfile = &file->cf_Next;
  591. }
  592. }
  593. }
  594. /*
  595. * TestJobs()
  596. *
  597. * determine which jobs need to be run. Under normal conditions, the
  598. * period is about a minute (one scan). Worst case it will be one
  599. * hour (60 scans).
  600. */
  601. static int TestJobs(time_t t1, time_t t2)
  602. {
  603. int nJobs = 0;
  604. time_t t;
  605. /* Find jobs > t1 and <= t2 */
  606. for (t = t1 - t1 % 60; t <= t2; t += 60) {
  607. if (t > t1) {
  608. struct tm *tp = localtime(&t);
  609. CronFile *file;
  610. CronLine *line;
  611. for (file = FileBase; file; file = file->cf_Next) {
  612. #if ENABLE_DEBUG_CROND_OPTION
  613. if (DebugOpt)
  614. crondlog("\005FILE %s:\n", file->cf_User);
  615. #endif
  616. if (file->cf_Deleted)
  617. continue;
  618. for (line = file->cf_LineBase; line; line = line->cl_Next) {
  619. #if ENABLE_DEBUG_CROND_OPTION
  620. if (DebugOpt)
  621. crondlog("\005 LINE %s\n", line->cl_Shell);
  622. #endif
  623. if (line->cl_Mins[tp->tm_min] && line->cl_Hrs[tp->tm_hour] &&
  624. (line->cl_Days[tp->tm_mday] || line->cl_Dow[tp->tm_wday])
  625. && line->cl_Mons[tp->tm_mon]) {
  626. #if ENABLE_DEBUG_CROND_OPTION
  627. if (DebugOpt) {
  628. crondlog("\005 JobToDo: %d %s\n",
  629. line->cl_Pid, line->cl_Shell);
  630. }
  631. #endif
  632. if (line->cl_Pid > 0) {
  633. crondlog("\010 process already running: %s %s\n",
  634. file->cf_User, line->cl_Shell);
  635. } else if (line->cl_Pid == 0) {
  636. line->cl_Pid = -1;
  637. file->cf_Ready = 1;
  638. ++nJobs;
  639. }
  640. }
  641. }
  642. }
  643. }
  644. }
  645. return nJobs;
  646. }
  647. static void RunJobs(void)
  648. {
  649. CronFile *file;
  650. CronLine *line;
  651. for (file = FileBase; file; file = file->cf_Next) {
  652. if (file->cf_Ready) {
  653. file->cf_Ready = 0;
  654. for (line = file->cf_LineBase; line; line = line->cl_Next) {
  655. if (line->cl_Pid < 0) {
  656. RunJob(file->cf_User, line);
  657. crondlog("\010USER %s pid %3d cmd %s\n",
  658. file->cf_User, line->cl_Pid, line->cl_Shell);
  659. if (line->cl_Pid < 0) {
  660. file->cf_Ready = 1;
  661. }
  662. else if (line->cl_Pid > 0) {
  663. file->cf_Running = 1;
  664. }
  665. }
  666. }
  667. }
  668. }
  669. }
  670. /*
  671. * CheckJobs() - check for job completion
  672. *
  673. * Check for job completion, return number of jobs still running after
  674. * all done.
  675. */
  676. static int CheckJobs(void)
  677. {
  678. CronFile *file;
  679. CronLine *line;
  680. int nStillRunning = 0;
  681. for (file = FileBase; file; file = file->cf_Next) {
  682. if (file->cf_Running) {
  683. file->cf_Running = 0;
  684. for (line = file->cf_LineBase; line; line = line->cl_Next) {
  685. if (line->cl_Pid > 0) {
  686. int status;
  687. int r = wait4(line->cl_Pid, &status, WNOHANG, NULL);
  688. if (r < 0 || r == line->cl_Pid) {
  689. EndJob(file->cf_User, line);
  690. if (line->cl_Pid) {
  691. file->cf_Running = 1;
  692. }
  693. } else if (r == 0) {
  694. file->cf_Running = 1;
  695. }
  696. }
  697. }
  698. }
  699. nStillRunning += file->cf_Running;
  700. }
  701. return nStillRunning;
  702. }
  703. #if ENABLE_FEATURE_CROND_CALL_SENDMAIL
  704. static void
  705. ForkJob(const char *user, CronLine * line, int mailFd,
  706. const char *prog, const char *cmd, const char *arg, const char *mailf)
  707. {
  708. /* Fork as the user in question and run program */
  709. pid_t pid = fork();
  710. line->cl_Pid = pid;
  711. if (pid == 0) {
  712. /* CHILD */
  713. /* Change running state to the user in question */
  714. if (ChangeUser(user) < 0) {
  715. exit(0);
  716. }
  717. #if ENABLE_DEBUG_CROND_OPTION
  718. if (DebugOpt) {
  719. crondlog("\005Child Running %s\n", prog);
  720. }
  721. #endif
  722. if (mailFd >= 0) {
  723. dup2(mailFd, mailf != NULL);
  724. dup2((mailf ? mailFd : 1), 2);
  725. close(mailFd);
  726. }
  727. execl(prog, prog, cmd, arg, NULL);
  728. crondlog("\024cannot exec, user %s cmd %s %s %s\n", user, prog, cmd, arg);
  729. if (mailf) {
  730. fdprintf(1, "Exec failed: %s -c %s\n", prog, arg);
  731. }
  732. exit(0);
  733. } else if (pid < 0) {
  734. /* FORK FAILED */
  735. crondlog("\024cannot fork, user %s\n", user);
  736. line->cl_Pid = 0;
  737. if (mailf) {
  738. remove(mailf);
  739. }
  740. } else if (mailf) {
  741. /* PARENT, FORK SUCCESS
  742. * rename mail-file based on pid of process
  743. */
  744. char mailFile2[128];
  745. snprintf(mailFile2, sizeof(mailFile2), TMPDIR "/cron.%s.%d", user, pid);
  746. rename(mailf, mailFile2);
  747. }
  748. /*
  749. * Close the mail file descriptor.. we can't just leave it open in
  750. * a structure, closing it later, because we might run out of descriptors
  751. */
  752. if (mailFd >= 0) {
  753. close(mailFd);
  754. }
  755. }
  756. static void RunJob(const char *user, CronLine * line)
  757. {
  758. char mailFile[128];
  759. int mailFd;
  760. line->cl_Pid = 0;
  761. line->cl_MailFlag = 0;
  762. /* open mail file - owner root so nobody can screw with it. */
  763. snprintf(mailFile, sizeof(mailFile), TMPDIR "/cron.%s.%d", user, getpid());
  764. mailFd = open(mailFile, O_CREAT | O_TRUNC | O_WRONLY | O_EXCL | O_APPEND, 0600);
  765. if (mailFd >= 0) {
  766. line->cl_MailFlag = 1;
  767. fdprintf(mailFd, "To: %s\nSubject: cron: %s\n\n", user,
  768. line->cl_Shell);
  769. line->cl_MailPos = lseek(mailFd, 0, SEEK_CUR);
  770. } else {
  771. crondlog("\024cannot create mail file user %s file %s, output to /dev/null\n", user, mailFile);
  772. }
  773. ForkJob(user, line, mailFd, DEFAULT_SHELL, "-c", line->cl_Shell, mailFile);
  774. }
  775. /*
  776. * EndJob - called when job terminates and when mail terminates
  777. */
  778. static void EndJob(const char *user, CronLine * line)
  779. {
  780. int mailFd;
  781. char mailFile[128];
  782. struct stat sbuf;
  783. /* No job */
  784. if (line->cl_Pid <= 0) {
  785. line->cl_Pid = 0;
  786. return;
  787. }
  788. /*
  789. * End of job and no mail file
  790. * End of sendmail job
  791. */
  792. snprintf(mailFile, sizeof(mailFile), TMPDIR "/cron.%s.%d", user, line->cl_Pid);
  793. line->cl_Pid = 0;
  794. if (line->cl_MailFlag != 1) {
  795. return;
  796. }
  797. line->cl_MailFlag = 0;
  798. /*
  799. * End of primary job - check for mail file. If size has increased and
  800. * the file is still valid, we sendmail it.
  801. */
  802. mailFd = open(mailFile, O_RDONLY);
  803. remove(mailFile);
  804. if (mailFd < 0) {
  805. return;
  806. }
  807. if (fstat(mailFd, &sbuf) < 0 || sbuf.st_uid != DaemonUid || sbuf.st_nlink != 0 ||
  808. sbuf.st_size == line->cl_MailPos || !S_ISREG(sbuf.st_mode)) {
  809. close(mailFd);
  810. return;
  811. }
  812. ForkJob(user, line, mailFd, SENDMAIL, SENDMAIL_ARGS, NULL);
  813. }
  814. #else
  815. /* crond without sendmail */
  816. static void RunJob(const char *user, CronLine * line)
  817. {
  818. /* Fork as the user in question and run program */
  819. pid_t pid = fork();
  820. if (pid == 0) {
  821. /* CHILD */
  822. /* Change running state to the user in question */
  823. if (ChangeUser(user) < 0) {
  824. exit(0);
  825. }
  826. #if ENABLE_DEBUG_CROND_OPTION
  827. if (DebugOpt) {
  828. crondlog("\005Child Running %s\n", DEFAULT_SHELL);
  829. }
  830. #endif
  831. execl(DEFAULT_SHELL, DEFAULT_SHELL, "-c", line->cl_Shell, NULL);
  832. crondlog("\024cannot exec, user %s cmd %s -c %s\n", user,
  833. DEFAULT_SHELL, line->cl_Shell);
  834. exit(0);
  835. } else if (pid < 0) {
  836. /* FORK FAILED */
  837. crondlog("\024cannot, user %s\n", user);
  838. pid = 0;
  839. }
  840. line->cl_Pid = pid;
  841. }
  842. #endif /* ENABLE_FEATURE_CROND_CALL_SENDMAIL */