crond.c 29 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133
  1. /* vi: set sw=4 ts=4: */
  2. /*
  3. * run as root, but NOT setuid root
  4. *
  5. * Copyright 1994 Matthew Dillon (dillon@apollo.west.oic.com)
  6. * (version 2.3.2)
  7. * Vladimir Oleynik <dzo@simtreas.ru> (C) 2002
  8. *
  9. * Licensed under GPLv2 or later, see file LICENSE in this source tree.
  10. */
  11. //config:config CROND
  12. //config: bool "crond (15 kb)"
  13. //config: default y
  14. //config: select FEATURE_SYSLOG
  15. //config: help
  16. //config: Crond is a background daemon that parses individual crontab
  17. //config: files and executes commands on behalf of the users in question.
  18. //config: This is a port of dcron from slackware. It uses files of the
  19. //config: format /var/spool/cron/crontabs/<username> files, for example:
  20. //config: $ cat /var/spool/cron/crontabs/root
  21. //config: # Run daily cron jobs at 4:40 every day:
  22. //config: 40 4 * * * /etc/cron/daily > /dev/null 2>&1
  23. //config:
  24. //config:config FEATURE_CROND_D
  25. //config: bool "Support -d (redirect output to stderr)"
  26. //config: depends on CROND
  27. //config: default y
  28. //config: help
  29. //config: -d N sets loglevel (0:most verbose) and directs all output to stderr.
  30. //config:
  31. //config:config FEATURE_CROND_CALL_SENDMAIL
  32. //config: bool "Report command output via email (using sendmail)"
  33. //config: default y
  34. //config: depends on CROND
  35. //config: help
  36. //config: Command output will be sent to corresponding user via email.
  37. //config:
  38. //config:config FEATURE_CROND_SPECIAL_TIMES
  39. //config: bool "Support special times (@reboot, @daily, etc) in crontabs"
  40. //config: default y
  41. //config: depends on CROND
  42. //config: help
  43. //config: string meaning
  44. //config: ------ -------
  45. //config: @reboot Run once, at startup
  46. //config: @yearly Run once a year: "0 0 1 1 *"
  47. //config: @annually Same as @yearly: "0 0 1 1 *"
  48. //config: @monthly Run once a month: "0 0 1 * *"
  49. //config: @weekly Run once a week: "0 0 * * 0"
  50. //config: @daily Run once a day: "0 0 * * *"
  51. //config: @midnight Same as @daily: "0 0 * * *"
  52. //config: @hourly Run once an hour: "0 * * * *"
  53. //config:
  54. //config:config FEATURE_CROND_DIR
  55. //config: string "crond spool directory"
  56. //config: default "/var/spool/cron"
  57. //config: depends on CROND || CRONTAB
  58. //config: help
  59. //config: Location of crond spool.
  60. //applet:IF_CROND(APPLET(crond, BB_DIR_USR_SBIN, BB_SUID_DROP))
  61. //kbuild:lib-$(CONFIG_CROND) += crond.o
  62. //usage:#define crond_trivial_usage
  63. //usage: "[-fbS] [-l N] " IF_FEATURE_CROND_D("[-d N] ") "[-L LOGFILE] [-c DIR]"
  64. //usage:#define crond_full_usage "\n\n"
  65. //usage: " -f Foreground"
  66. //usage: "\n -b Background (default)"
  67. //usage: "\n -S Log to syslog (default)"
  68. //usage: "\n -l N Set log level. Most verbose 0, default 8"
  69. //usage: IF_FEATURE_CROND_D(
  70. //usage: "\n -d N Set log level, log to stderr"
  71. //usage: )
  72. //usage: "\n -L FILE Log to FILE"
  73. //usage: "\n -c DIR Cron dir. Default:"CONFIG_FEATURE_CROND_DIR"/crontabs"
  74. #include "libbb.h"
  75. #include "common_bufsiz.h"
  76. #include <syslog.h>
  77. #if 0
  78. /* If libc tracks and reuses setenv()-allocated memory, ok to set this to 0 */
  79. /* Neither glibc nor uclibc do that! */
  80. # define SETENV_LEAKS 0
  81. #else
  82. # define SETENV_LEAKS 1
  83. #endif
  84. #define CRON_DIR CONFIG_FEATURE_CROND_DIR
  85. #define CRONTABS CONFIG_FEATURE_CROND_DIR "/crontabs"
  86. #define CRON_REBOOT CONFIG_PID_FILE_PATH "/crond.reboot"
  87. #ifndef SENDMAIL
  88. # define SENDMAIL "sendmail"
  89. #endif
  90. #ifndef SENDMAIL_ARGS
  91. # define SENDMAIL_ARGS "-ti"
  92. #endif
  93. #ifndef CRONUPDATE
  94. # define CRONUPDATE "cron.update"
  95. #endif
  96. #ifndef MAXLINES
  97. # define MAXLINES 256 /* max lines in non-root crontabs */
  98. #endif
  99. typedef struct CronFile {
  100. struct CronFile *cf_next;
  101. struct CronLine *cf_lines;
  102. char *cf_username;
  103. smallint cf_wants_starting; /* bool: one or more jobs ready */
  104. smallint cf_has_running; /* bool: one or more jobs running */
  105. smallint cf_deleted; /* marked for deletion (but still has running jobs) */
  106. } CronFile;
  107. typedef struct CronLine {
  108. struct CronLine *cl_next;
  109. char *cl_cmd; /* shell command */
  110. pid_t cl_pid; /* >0:running, <0:needs to be started in this minute, 0:dormant */
  111. #define START_ME_REBOOT -2
  112. #define START_ME_NORMAL -1
  113. #if ENABLE_FEATURE_CROND_CALL_SENDMAIL
  114. int cl_empty_mail_size; /* size of mail header only, 0 if no mailfile */
  115. char *cl_mailto; /* whom to mail results, may be NULL */
  116. #endif
  117. char *cl_shell;
  118. char *cl_path;
  119. /* ordered by size, not in natural order. makes code smaller: */
  120. char cl_Dow[7]; /* 0-6, beginning sunday */
  121. char cl_Mons[12]; /* 0-11 */
  122. char cl_Hrs[24]; /* 0-23 */
  123. char cl_Days[32]; /* 1-31 */
  124. char cl_Mins[60]; /* 0-59 */
  125. } CronLine;
  126. #define DAEMON_UID 0
  127. enum {
  128. OPT_l = (1 << 0),
  129. OPT_L = (1 << 1),
  130. OPT_f = (1 << 2),
  131. OPT_b = (1 << 3),
  132. OPT_S = (1 << 4),
  133. OPT_c = (1 << 5),
  134. OPT_d = (1 << 6) * ENABLE_FEATURE_CROND_D,
  135. };
  136. struct globals {
  137. unsigned log_level; /* = 8; */
  138. time_t crontab_dir_mtime;
  139. const char *log_filename;
  140. const char *crontab_dir_name; /* = CRONTABS; */
  141. CronFile *cron_files;
  142. char *default_shell;
  143. #if SETENV_LEAKS
  144. char *env_var_user;
  145. char *env_var_home;
  146. char *env_var_shell;
  147. char *env_var_logname;
  148. #endif
  149. } FIX_ALIASING;
  150. #define G (*(struct globals*)bb_common_bufsiz1)
  151. #define INIT_G() do { \
  152. setup_common_bufsiz(); \
  153. G.log_level = 8; \
  154. G.crontab_dir_name = CRONTABS; \
  155. } while (0)
  156. /* Log levels:
  157. * 0 is the most verbose, default 8.
  158. * For some reason, in fact only 5, 7 and 8 are used.
  159. */
  160. static void crondlog(unsigned level, const char *msg, va_list va)
  161. {
  162. if (level >= G.log_level) {
  163. /*
  164. * We are called only for info meesages.
  165. * Warnings/errors use plain bb_[p]error_msg's, which
  166. * need not touch syslog_level
  167. * (they are ok with LOG_ERR default).
  168. */
  169. bb_vinfo_msg(msg, va);
  170. }
  171. }
  172. static void log5(const char *msg, ...)
  173. {
  174. va_list va;
  175. va_start(va, msg);
  176. crondlog(5, msg, va);
  177. va_end(va);
  178. }
  179. static void log7(const char *msg, ...)
  180. {
  181. va_list va;
  182. va_start(va, msg);
  183. crondlog(7, msg, va);
  184. va_end(va);
  185. }
  186. static void log8(const char *msg, ...)
  187. {
  188. va_list va;
  189. va_start(va, msg);
  190. crondlog(8, msg, va);
  191. va_end(va);
  192. }
  193. static const char DowAry[] ALIGN1 =
  194. "sun""mon""tue""wed""thu""fri""sat"
  195. ;
  196. static const char MonAry[] ALIGN1 =
  197. "jan""feb""mar""apr""may""jun""jul""aug""sep""oct""nov""dec"
  198. ;
  199. static void ParseField(char *user, char *ary, int modvalue, int off,
  200. const char *names, char *ptr)
  201. /* 'names' is a pointer to a set of 3-char abbreviations */
  202. {
  203. char *base = ptr;
  204. int n1 = -1;
  205. int n2 = -1;
  206. // this can't happen due to config_read()
  207. /*if (base == NULL)
  208. return;*/
  209. while (1) {
  210. int skip = 0;
  211. /* Handle numeric digit or symbol or '*' */
  212. if (*ptr == '*') {
  213. n1 = 0; /* everything will be filled */
  214. n2 = modvalue - 1;
  215. skip = 1;
  216. ++ptr;
  217. } else if (isdigit(*ptr)) {
  218. char *endp;
  219. if (n1 < 0) {
  220. n1 = strtol(ptr, &endp, 10) + off;
  221. } else {
  222. n2 = strtol(ptr, &endp, 10) + off;
  223. }
  224. ptr = endp; /* gcc likes temp var for &endp */
  225. skip = 1;
  226. } else if (names) {
  227. int i;
  228. for (i = 0; names[i]; i += 3) {
  229. /* was using strncmp before... */
  230. if (strncasecmp(ptr, &names[i], 3) == 0) {
  231. ptr += 3;
  232. if (n1 < 0) {
  233. n1 = i / 3;
  234. } else {
  235. n2 = i / 3;
  236. }
  237. skip = 1;
  238. break;
  239. }
  240. }
  241. }
  242. /* handle optional range '-' */
  243. if (skip == 0) {
  244. goto err;
  245. }
  246. if (*ptr == '-' && n2 < 0) {
  247. ++ptr;
  248. continue;
  249. }
  250. /*
  251. * collapse single-value ranges, handle skipmark, and fill
  252. * in the character array appropriately.
  253. */
  254. if (n2 < 0) {
  255. n2 = n1;
  256. }
  257. if (*ptr == '/') {
  258. char *endp;
  259. skip = strtol(ptr + 1, &endp, 10);
  260. ptr = endp; /* gcc likes temp var for &endp */
  261. }
  262. /*
  263. * fill array, using a failsafe is the easiest way to prevent
  264. * an endless loop
  265. */
  266. {
  267. int s0 = 1;
  268. int failsafe = 1024;
  269. --n1;
  270. do {
  271. n1 = (n1 + 1) % modvalue;
  272. if (--s0 == 0) {
  273. ary[n1 % modvalue] = 1;
  274. s0 = skip;
  275. }
  276. if (--failsafe == 0) {
  277. goto err;
  278. }
  279. } while (n1 != n2);
  280. }
  281. if (*ptr != ',') {
  282. break;
  283. }
  284. ++ptr;
  285. n1 = -1;
  286. n2 = -1;
  287. }
  288. if (*ptr) {
  289. err:
  290. bb_error_msg("user %s: parse error at %s", user, base);
  291. return;
  292. }
  293. /* can't use log5 (it inserts newlines), open-coding it */
  294. if (G.log_level <= 5 && logmode != LOGMODE_SYSLOG) {
  295. int i;
  296. for (i = 0; i < modvalue; ++i)
  297. fprintf(stderr, "%d", (unsigned char)ary[i]);
  298. bb_putchar_stderr('\n');
  299. }
  300. }
  301. static void FixDayDow(CronLine *line)
  302. {
  303. unsigned i;
  304. int weekUsed = 0;
  305. int daysUsed = 0;
  306. for (i = 0; i < ARRAY_SIZE(line->cl_Dow); ++i) {
  307. if (line->cl_Dow[i] == 0) {
  308. weekUsed = 1;
  309. break;
  310. }
  311. }
  312. for (i = 0; i < ARRAY_SIZE(line->cl_Days); ++i) {
  313. if (line->cl_Days[i] == 0) {
  314. daysUsed = 1;
  315. break;
  316. }
  317. }
  318. if (weekUsed != daysUsed) {
  319. if (weekUsed)
  320. memset(line->cl_Days, 0, sizeof(line->cl_Days));
  321. else /* daysUsed */
  322. memset(line->cl_Dow, 0, sizeof(line->cl_Dow));
  323. }
  324. }
  325. /*
  326. * delete_cronfile() - delete user database
  327. *
  328. * Note: multiple entries for same user may exist if we were unable to
  329. * completely delete a database due to running processes.
  330. */
  331. //FIXME: we will start a new job even if the old job is running
  332. //if crontab was reloaded: crond thinks that "new" job is different from "old"
  333. //even if they are in fact completely the same. Example
  334. //Crontab was:
  335. // 0-59 * * * * job1
  336. // 0-59 * * * * long_running_job2
  337. //User edits crontab to:
  338. // 0-59 * * * * job1_updated
  339. // 0-59 * * * * long_running_job2
  340. //Bug: crond can now start another long_running_job2 even if old one
  341. //is still running.
  342. //OTOH most other versions of cron do not wait for job termination anyway,
  343. //they end up with multiple copies of jobs if they don't terminate soon enough.
  344. static void delete_cronfile(const char *userName)
  345. {
  346. CronFile **pfile = &G.cron_files;
  347. CronFile *file;
  348. while ((file = *pfile) != NULL) {
  349. if (strcmp(userName, file->cf_username) == 0) {
  350. CronLine **pline = &file->cf_lines;
  351. CronLine *line;
  352. file->cf_has_running = 0;
  353. file->cf_deleted = 1;
  354. while ((line = *pline) != NULL) {
  355. if (line->cl_pid > 0) {
  356. file->cf_has_running = 1;
  357. pline = &line->cl_next;
  358. } else {
  359. *pline = line->cl_next;
  360. free(line->cl_cmd);
  361. free(line);
  362. }
  363. }
  364. if (file->cf_has_running == 0) {
  365. *pfile = file->cf_next;
  366. free(file->cf_username);
  367. free(file);
  368. continue;
  369. }
  370. }
  371. pfile = &file->cf_next;
  372. }
  373. }
  374. static void load_crontab(const char *fileName)
  375. {
  376. struct parser_t *parser;
  377. struct stat sbuf;
  378. int maxLines;
  379. char *tokens[6];
  380. #if ENABLE_FEATURE_CROND_CALL_SENDMAIL
  381. char *mailTo = NULL;
  382. #endif
  383. char *shell = NULL;
  384. char *path = NULL;
  385. delete_cronfile(fileName);
  386. if (!getpwnam(fileName)) {
  387. log7("ignoring file '%s' (no such user)", fileName);
  388. return;
  389. }
  390. parser = config_open(fileName);
  391. if (!parser)
  392. return;
  393. maxLines = (strcmp(fileName, "root") == 0) ? 65535 : MAXLINES;
  394. if (fstat(fileno(parser->fp), &sbuf) == 0 && sbuf.st_uid == DAEMON_UID) {
  395. CronFile *file = xzalloc(sizeof(CronFile));
  396. CronLine **pline;
  397. int n;
  398. file->cf_username = xstrdup(fileName);
  399. pline = &file->cf_lines;
  400. while (1) {
  401. CronLine *line;
  402. if (!--maxLines) {
  403. bb_error_msg("user %s: too many lines", fileName);
  404. break;
  405. }
  406. n = config_read(parser, tokens, 6, 1, "# \t", PARSE_NORMAL | PARSE_KEEP_COPY);
  407. if (!n)
  408. break;
  409. log5("user:%s entry:%s", fileName, parser->data);
  410. /* check if line is setting MAILTO= */
  411. if (is_prefixed_with(tokens[0], "MAILTO=")) {
  412. #if ENABLE_FEATURE_CROND_CALL_SENDMAIL
  413. free(mailTo);
  414. mailTo = (tokens[0][7]) ? xstrdup(&tokens[0][7]) : NULL;
  415. #endif /* otherwise just ignore such lines */
  416. continue;
  417. }
  418. if (is_prefixed_with(tokens[0], "SHELL=")) {
  419. free(shell);
  420. shell = xstrdup(&tokens[0][6]);
  421. continue;
  422. }
  423. if (is_prefixed_with(tokens[0], "PATH=")) {
  424. free(path);
  425. path = xstrdup(&tokens[0][5]);
  426. continue;
  427. }
  428. //TODO: handle HOME= too? Better yet, handle arbitrary ENVVARs? "man crontab" says:
  429. //name = value
  430. //
  431. //where the spaces around the equal-sign (=) are optional, and any subsequent
  432. //non-leading spaces in value will be part of the value assigned to name.
  433. //The value string may be placed in quotes (single or double, but matching)
  434. //to preserve leading or trailing blanks.
  435. //
  436. //Several environment variables are set up automatically by the cron(8) daemon.
  437. //SHELL is set to /bin/sh, and LOGNAME and HOME are set from the /etc/passwd
  438. //line of the crontab's owner. HOME, SHELL, and PATH may be overridden by
  439. //settings in the crontab; LOGNAME may not.
  440. #if ENABLE_FEATURE_CROND_SPECIAL_TIMES
  441. if (tokens[0][0] == '@') {
  442. /*
  443. * "@daily /a/script/to/run PARAM1 PARAM2..."
  444. */
  445. typedef struct SpecialEntry {
  446. const char *name;
  447. const char tokens[8];
  448. } SpecialEntry;
  449. static const SpecialEntry SpecAry[] ALIGN8 = {
  450. /* hour day month weekday */
  451. { "yearly", "0\0" "1\0" "1\0" "*" },
  452. { "annually", "0\0" "1\0" "1\0" "*" },
  453. { "monthly", "0\0" "1\0" "*\0" "*" },
  454. { "weekly", "0\0" "*\0" "*\0" "0" },
  455. { "daily", "0\0" "*\0" "*\0" "*" },
  456. { "midnight", "0\0" "*\0" "*\0" "*" },
  457. { "hourly", "*\0" "*\0" "*\0" "*" },
  458. { "reboot", "" },
  459. };
  460. const SpecialEntry *e = SpecAry;
  461. if (n < 2)
  462. continue;
  463. for (;;) {
  464. if (strcmp(e->name, tokens[0] + 1) == 0) {
  465. /*
  466. * tokens[1] is only the first word of command,
  467. * can'r use it.
  468. * find the entire command in unmodified string:
  469. */
  470. tokens[5] = skip_whitespace(
  471. skip_non_whitespace(
  472. skip_whitespace(parser->data)));
  473. if (e->tokens[0]) {
  474. char *et = (char*)e->tokens;
  475. /* minute is "0" for all specials */
  476. tokens[0] = (char*)"0";
  477. tokens[1] = et;
  478. tokens[2] = et + 2;
  479. tokens[3] = et + 4;
  480. tokens[4] = et + 6;
  481. }
  482. goto got_it;
  483. }
  484. if (!e->tokens[0])
  485. break;
  486. e++;
  487. }
  488. continue; /* bad line (unrecognized '@foo') */
  489. }
  490. #endif
  491. /* check if a minimum of tokens is specified */
  492. if (n < 6)
  493. continue;
  494. IF_FEATURE_CROND_SPECIAL_TIMES(
  495. got_it:
  496. )
  497. *pline = line = xzalloc(sizeof(*line));
  498. #if ENABLE_FEATURE_CROND_SPECIAL_TIMES
  499. if (tokens[0][0] == '@') { /* "@reboot" line */
  500. file->cf_wants_starting = 1;
  501. line->cl_pid = START_ME_REBOOT; /* wants to start */
  502. /* line->cl_Mins/Hrs/etc stay zero: never match any time */
  503. } else
  504. #endif
  505. {
  506. /* parse date ranges */
  507. ParseField(file->cf_username, line->cl_Mins, 60, 0, NULL, tokens[0]);
  508. ParseField(file->cf_username, line->cl_Hrs, 24, 0, NULL, tokens[1]);
  509. ParseField(file->cf_username, line->cl_Days, 32, 0, NULL, tokens[2]);
  510. ParseField(file->cf_username, line->cl_Mons, 12, -1, MonAry, tokens[3]);
  511. ParseField(file->cf_username, line->cl_Dow, 7, 0, DowAry, tokens[4]);
  512. /*
  513. * fix days and dow - if one is not "*" and the other
  514. * is "*", the other is set to 0, and vise-versa
  515. */
  516. FixDayDow(line);
  517. }
  518. #if ENABLE_FEATURE_CROND_CALL_SENDMAIL
  519. /* copy mailto (can be NULL) */
  520. line->cl_mailto = xstrdup(mailTo);
  521. #endif
  522. line->cl_shell = xstrdup(shell);
  523. line->cl_path = xstrdup(path);
  524. /* copy command */
  525. line->cl_cmd = xstrdup(tokens[5]);
  526. pline = &line->cl_next;
  527. //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]);
  528. }
  529. *pline = NULL;
  530. file->cf_next = G.cron_files;
  531. G.cron_files = file;
  532. }
  533. config_close(parser);
  534. #if ENABLE_FEATURE_CROND_CALL_SENDMAIL
  535. free(mailTo);
  536. #endif
  537. free(shell);
  538. }
  539. static void process_cron_update_file(void)
  540. {
  541. FILE *fi;
  542. char buf[256];
  543. fi = fopen_for_read(CRONUPDATE);
  544. if (fi != NULL) {
  545. unlink(CRONUPDATE);
  546. while (fgets(buf, sizeof(buf), fi) != NULL) {
  547. /* use first word only */
  548. skip_non_whitespace(buf)[0] = '\0';
  549. load_crontab(buf);
  550. }
  551. fclose(fi);
  552. }
  553. }
  554. static void rescan_crontab_dir(void)
  555. {
  556. CronFile *file;
  557. /* Delete all files until we only have ones with running jobs (or none) */
  558. again:
  559. for (file = G.cron_files; file; file = file->cf_next) {
  560. if (!file->cf_deleted) {
  561. delete_cronfile(file->cf_username);
  562. goto again;
  563. }
  564. }
  565. /* Remove cron update file */
  566. unlink(CRONUPDATE);
  567. /* Re-chdir, in case directory was renamed & deleted */
  568. xchdir(G.crontab_dir_name);
  569. /* Scan directory and add associated users */
  570. {
  571. DIR *dir = opendir(".");
  572. struct dirent *den;
  573. /* xopendir exists, but "can't open '.'" is not informative */
  574. if (!dir)
  575. bb_error_msg_and_die("can't open '%s'", G.crontab_dir_name);
  576. while ((den = readdir(dir)) != NULL) {
  577. if (strchr(den->d_name, '.') != NULL) {
  578. continue;
  579. }
  580. load_crontab(den->d_name);
  581. }
  582. closedir(dir);
  583. }
  584. }
  585. #if SETENV_LEAKS
  586. /* We set environment *before* vfork (because we want to use vfork),
  587. * so we cannot use setenv() - repeated calls to setenv() may leak memory!
  588. * Using putenv(), and freeing memory after unsetenv() won't leak */
  589. static void safe_setenv(char **pvar_val, const char *var, const char *val)
  590. {
  591. char *var_val = *pvar_val;
  592. if (var_val) {
  593. bb_unsetenv_and_free(var_val);
  594. }
  595. *pvar_val = xasprintf("%s=%s", var, val);
  596. putenv(*pvar_val);
  597. }
  598. #endif
  599. static void set_env_vars(struct passwd *pas, const char *shell, const char *path)
  600. {
  601. /* POSIX requires crond to set up at least HOME, LOGNAME, PATH, SHELL.
  602. */
  603. #if SETENV_LEAKS
  604. safe_setenv(&G.env_var_logname, "LOGNAME", pas->pw_name);
  605. safe_setenv(&G.env_var_user, "USER", pas->pw_name);
  606. safe_setenv(&G.env_var_home, "HOME", pas->pw_dir);
  607. safe_setenv(&G.env_var_shell, "SHELL", shell);
  608. if (path) safe_setenv(&G.env_var_shell, "PATH", path);
  609. #else
  610. xsetenv("LOGNAME", pas->pw_name);
  611. xsetenv("USER", pas->pw_name);
  612. xsetenv("HOME", pas->pw_dir);
  613. xsetenv("SHELL", shell);
  614. if (path) xsetenv("PATH", path);
  615. #endif
  616. }
  617. static void change_user(struct passwd *pas)
  618. {
  619. /* careful: we're after vfork! */
  620. change_identity(pas); /* - initgroups, setgid, setuid */
  621. if (chdir_or_warn(pas->pw_dir) != 0) {
  622. xchdir(CRON_DIR);
  623. }
  624. }
  625. // TODO: sendmail should be _run-time_ option, not compile-time!
  626. #if ENABLE_FEATURE_CROND_CALL_SENDMAIL
  627. static pid_t
  628. fork_job(const char *user, int mailFd, CronLine *line, bool run_sendmail)
  629. {
  630. struct passwd *pas;
  631. const char *shell, *prog;
  632. smallint sv_logmode;
  633. pid_t pid;
  634. /* prepare things before vfork */
  635. pas = getpwnam(user);
  636. if (!pas) {
  637. bb_error_msg("can't get uid for %s", user);
  638. goto err;
  639. }
  640. shell = line->cl_shell ? line->cl_shell : G.default_shell;
  641. prog = run_sendmail ? SENDMAIL : shell;
  642. set_env_vars(pas, shell, NULL); /* don't use crontab's PATH for sendmail */
  643. sv_logmode = logmode;
  644. pid = vfork();
  645. if (pid == 0) {
  646. /* CHILD */
  647. /* initgroups, setgid, setuid, and chdir to home or CRON_DIR */
  648. change_user(pas);
  649. log5("child running %s", prog);
  650. if (mailFd >= 0) {
  651. xmove_fd(mailFd, run_sendmail ? 0 : 1);
  652. dup2(1, 2);
  653. }
  654. /* crond 3.0pl1-100 puts tasks in separate process groups */
  655. bb_setpgrp();
  656. if (!run_sendmail)
  657. execlp(prog, prog, "-c", line->cl_cmd, (char *) NULL);
  658. else
  659. execlp(prog, prog, SENDMAIL_ARGS, (char *) NULL);
  660. /*
  661. * I want this error message on stderr too,
  662. * even if other messages go only to syslog:
  663. */
  664. logmode |= LOGMODE_STDIO;
  665. bb_error_msg_and_die("can't execute '%s' for user %s", prog, user);
  666. }
  667. logmode = sv_logmode;
  668. if (pid < 0) {
  669. bb_simple_perror_msg("vfork");
  670. err:
  671. pid = 0;
  672. } /* else: PARENT, FORK SUCCESS */
  673. /*
  674. * Close the mail file descriptor.. we can't just leave it open in
  675. * a structure, closing it later, because we might run out of descriptors
  676. */
  677. if (mailFd >= 0) {
  678. close(mailFd);
  679. }
  680. return pid;
  681. }
  682. static pid_t start_one_job(const char *user, CronLine *line)
  683. {
  684. char mailFile[128];
  685. int mailFd = -1;
  686. line->cl_pid = 0;
  687. line->cl_empty_mail_size = 0;
  688. if (line->cl_mailto) {
  689. /* Open mail file (owner is root so nobody can screw with it) */
  690. snprintf(mailFile, sizeof(mailFile), "%s/cron.%s.%d", CRON_DIR, user, getpid());
  691. mailFd = open(mailFile, O_CREAT | O_TRUNC | O_WRONLY | O_EXCL | O_APPEND, 0600);
  692. if (mailFd >= 0) {
  693. fdprintf(mailFd, "To: %s\nSubject: cron: %s\n\n", line->cl_mailto,
  694. line->cl_cmd);
  695. line->cl_empty_mail_size = lseek(mailFd, 0, SEEK_CUR);
  696. } else {
  697. bb_error_msg("can't create mail file %s for user %s, "
  698. "discarding output", mailFile, user);
  699. }
  700. }
  701. line->cl_pid = fork_job(user, mailFd, line, /*sendmail?*/ 0);
  702. if (mailFd >= 0) {
  703. if (line->cl_pid <= 0) {
  704. unlink(mailFile);
  705. } else {
  706. /* rename mail-file based on pid of process */
  707. char *mailFile2 = xasprintf("%s/cron.%s.%d", CRON_DIR, user, (int)line->cl_pid);
  708. rename(mailFile, mailFile2); // TODO: xrename?
  709. free(mailFile2);
  710. }
  711. }
  712. return line->cl_pid;
  713. }
  714. /*
  715. * process_finished_job - called when job terminates and when mail terminates
  716. */
  717. static void process_finished_job(const char *user, CronLine *line)
  718. {
  719. pid_t pid;
  720. int mailFd;
  721. char mailFile[128];
  722. struct stat sbuf;
  723. pid = line->cl_pid;
  724. line->cl_pid = 0;
  725. if (pid <= 0) {
  726. /* No job */
  727. return;
  728. }
  729. if (line->cl_empty_mail_size <= 0) {
  730. /* End of job and no mail file, or end of sendmail job */
  731. return;
  732. }
  733. /*
  734. * End of primary job - check for mail file.
  735. * If size has changed and the file is still valid, we send it.
  736. */
  737. snprintf(mailFile, sizeof(mailFile), "%s/cron.%s.%d", CRON_DIR, user, (int)pid);
  738. mailFd = open(mailFile, O_RDONLY);
  739. unlink(mailFile);
  740. if (mailFd < 0) {
  741. return;
  742. }
  743. if (fstat(mailFd, &sbuf) < 0
  744. || sbuf.st_uid != DAEMON_UID
  745. || sbuf.st_nlink != 0
  746. || sbuf.st_size == line->cl_empty_mail_size
  747. || !S_ISREG(sbuf.st_mode)
  748. ) {
  749. close(mailFd);
  750. return;
  751. }
  752. line->cl_empty_mail_size = 0;
  753. /* if (line->cl_mailto) - always true if cl_empty_mail_size was nonzero */
  754. line->cl_pid = fork_job(user, mailFd, line, /*sendmail?*/ 1);
  755. }
  756. #else /* !ENABLE_FEATURE_CROND_CALL_SENDMAIL */
  757. static pid_t start_one_job(const char *user, CronLine *line)
  758. {
  759. const char *shell;
  760. struct passwd *pas;
  761. pid_t pid;
  762. pas = getpwnam(user);
  763. if (!pas) {
  764. bb_error_msg("can't get uid for %s", user);
  765. goto err;
  766. }
  767. /* Prepare things before vfork */
  768. shell = line->cl_shell ? line->cl_shell : G.default_shell;
  769. set_env_vars(pas, shell, line->cl_path);
  770. /* Fork as the user in question and run program */
  771. pid = vfork();
  772. if (pid == 0) {
  773. /* CHILD */
  774. /* initgroups, setgid, setuid, and chdir to home or CRON_DIR */
  775. change_user(pas);
  776. log5("child running %s", shell);
  777. /* crond 3.0pl1-100 puts tasks in separate process groups */
  778. bb_setpgrp();
  779. execl(shell, shell, "-c", line->cl_cmd, (char *) NULL);
  780. bb_error_msg_and_die("can't execute '%s' for user %s", shell, user);
  781. }
  782. if (pid < 0) {
  783. bb_simple_perror_msg("vfork");
  784. err:
  785. pid = 0;
  786. }
  787. line->cl_pid = pid;
  788. return pid;
  789. }
  790. #define process_finished_job(user, line) ((line)->cl_pid = 0)
  791. #endif /* !ENABLE_FEATURE_CROND_CALL_SENDMAIL */
  792. /*
  793. * Determine which jobs need to be run. Under normal conditions, the
  794. * period is about a minute (one scan). Worst case it will be one
  795. * hour (60 scans).
  796. */
  797. static void flag_starting_jobs(time_t t1, time_t t2)
  798. {
  799. time_t t;
  800. /* Find jobs > t1 and <= t2 */
  801. for (t = t1 - t1 % 60; t <= t2; t += 60) {
  802. struct tm *ptm;
  803. CronFile *file;
  804. CronLine *line;
  805. if (t <= t1)
  806. continue;
  807. ptm = localtime(&t);
  808. for (file = G.cron_files; file; file = file->cf_next) {
  809. log5("file %s:", file->cf_username);
  810. if (file->cf_deleted)
  811. continue;
  812. for (line = file->cf_lines; line; line = line->cl_next) {
  813. log5(" line %s", line->cl_cmd);
  814. if (line->cl_Mins[ptm->tm_min]
  815. && line->cl_Hrs[ptm->tm_hour]
  816. && (line->cl_Days[ptm->tm_mday] || line->cl_Dow[ptm->tm_wday])
  817. && line->cl_Mons[ptm->tm_mon]
  818. ) {
  819. log5(" job: %d %s",
  820. (int)line->cl_pid, line->cl_cmd);
  821. if (line->cl_pid > 0) {
  822. log8("user %s: process already running: %s",
  823. file->cf_username, line->cl_cmd);
  824. } else if (line->cl_pid == 0) {
  825. line->cl_pid = START_ME_NORMAL;
  826. file->cf_wants_starting = 1;
  827. }
  828. }
  829. }
  830. }
  831. }
  832. }
  833. #if ENABLE_FEATURE_CROND_SPECIAL_TIMES
  834. static int touch_reboot_file(void)
  835. {
  836. int fd = open(CRON_REBOOT, O_WRONLY | O_CREAT | O_EXCL | O_TRUNC, 0000);
  837. if (fd >= 0) {
  838. close(fd);
  839. return 1;
  840. }
  841. /* File (presumably) exists - this is not the first run after reboot */
  842. return 0;
  843. }
  844. #endif
  845. static void start_jobs(int wants_start)
  846. {
  847. CronFile *file;
  848. CronLine *line;
  849. for (file = G.cron_files; file; file = file->cf_next) {
  850. if (!file->cf_wants_starting)
  851. continue;
  852. file->cf_wants_starting = 0;
  853. for (line = file->cf_lines; line; line = line->cl_next) {
  854. pid_t pid;
  855. if (line->cl_pid != wants_start)
  856. continue;
  857. pid = start_one_job(file->cf_username, line);
  858. log8("USER %s pid %3d cmd %s",
  859. file->cf_username, (int)pid, line->cl_cmd);
  860. if (pid < 0) {
  861. file->cf_wants_starting = 1;
  862. }
  863. if (pid > 0) {
  864. file->cf_has_running = 1;
  865. }
  866. }
  867. }
  868. }
  869. /*
  870. * Check for job completion, return number of jobs still running after
  871. * all done.
  872. */
  873. static int check_completions(void)
  874. {
  875. CronFile *file;
  876. CronLine *line;
  877. int num_still_running = 0;
  878. for (file = G.cron_files; file; file = file->cf_next) {
  879. if (!file->cf_has_running)
  880. continue;
  881. file->cf_has_running = 0;
  882. for (line = file->cf_lines; line; line = line->cl_next) {
  883. int r;
  884. if (line->cl_pid <= 0)
  885. continue;
  886. r = waitpid(line->cl_pid, NULL, WNOHANG);
  887. if (r < 0 || r == line->cl_pid) {
  888. process_finished_job(file->cf_username, line);
  889. if (line->cl_pid == 0) {
  890. /* sendmail was not started for it */
  891. continue;
  892. }
  893. /* else: sendmail was started, job is still running, fall thru */
  894. }
  895. /* else: r == 0: "process is still running" */
  896. file->cf_has_running = 1;
  897. }
  898. //FIXME: if !file->cf_has_running && file->deleted: delete it!
  899. //otherwise deleted entries will stay forever, right?
  900. num_still_running += file->cf_has_running;
  901. }
  902. return num_still_running;
  903. }
  904. static void reopen_logfile_to_stderr(void)
  905. {
  906. if (G.log_filename) {
  907. int logfd = open_or_warn(G.log_filename, O_WRONLY | O_CREAT | O_APPEND);
  908. if (logfd >= 0)
  909. xmove_fd(logfd, STDERR_FILENO);
  910. }
  911. }
  912. int crond_main(int argc, char **argv) MAIN_EXTERNALLY_VISIBLE;
  913. int crond_main(int argc UNUSED_PARAM, char **argv)
  914. {
  915. time_t t2;
  916. unsigned rescan;
  917. unsigned sleep_time;
  918. unsigned opts;
  919. INIT_G();
  920. opts = getopt32(argv, "^"
  921. "l:L:fbSc:" IF_FEATURE_CROND_D("d:")
  922. "\0"
  923. /* "-b after -f is ignored", and so on for every pair a-b */
  924. "f-b:b-f:S-L:L-S" IF_FEATURE_CROND_D(":d-l")
  925. /* -l and -d have numeric param */
  926. ":l+" IF_FEATURE_CROND_D(":d+")
  927. ,
  928. &G.log_level, &G.log_filename, &G.crontab_dir_name
  929. IF_FEATURE_CROND_D(,&G.log_level)
  930. );
  931. /* both -d N and -l N set the same variable: G.log_level */
  932. if (!(opts & OPT_f)) {
  933. /* close stdin, stdout, stderr.
  934. * close unused descriptors - don't need them. */
  935. bb_daemonize_or_rexec(DAEMON_CLOSE_EXTRA_FDS, argv);
  936. }
  937. if (!(opts & OPT_d) && G.log_filename == NULL) {
  938. /* logging to syslog */
  939. openlog(applet_name, LOG_CONS | LOG_PID, LOG_CRON);
  940. logmode = LOGMODE_SYSLOG;
  941. }
  942. //signal(SIGHUP, SIG_IGN); /* ? original crond dies on HUP... */
  943. reopen_logfile_to_stderr();
  944. xchdir(G.crontab_dir_name);
  945. /* $SHELL, or current UID's shell, or DEFAULT_SHELL */
  946. /* Useful on Android where DEFAULT_SHELL /bin/sh may not exist */
  947. G.default_shell = xstrdup(get_shell_name());
  948. log8("crond (busybox "BB_VER") started, log level %d", G.log_level);
  949. rescan_crontab_dir();
  950. write_pidfile_std_path_and_ext("crond");
  951. #if ENABLE_FEATURE_CROND_SPECIAL_TIMES
  952. if (touch_reboot_file())
  953. start_jobs(START_ME_REBOOT); /* start @reboot entries, if any */
  954. #endif
  955. /* Main loop */
  956. t2 = time(NULL);
  957. rescan = 60;
  958. sleep_time = 60;
  959. for (;;) {
  960. struct stat sbuf;
  961. time_t t1;
  962. long dt;
  963. /* Synchronize to 1 minute, minimum 1 second */
  964. t1 = t2;
  965. sleep(sleep_time - (time(NULL) % sleep_time));
  966. t2 = time(NULL);
  967. dt = (long)t2 - (long)t1;
  968. reopen_logfile_to_stderr();
  969. /*
  970. * The file 'cron.update' is checked to determine new cron
  971. * jobs. The directory is rescanned once an hour to deal
  972. * with any screwups.
  973. *
  974. * Check for time jump. Disparities over an hour either way
  975. * result in resynchronization. A negative disparity
  976. * less than an hour causes us to effectively sleep until we
  977. * match the original time (i.e. no re-execution of jobs that
  978. * have just been run). A positive disparity less than
  979. * an hour causes intermediate jobs to be run, but only once
  980. * in the worst case.
  981. *
  982. * When running jobs, the inequality used is greater but not
  983. * equal to t1, and less then or equal to t2.
  984. */
  985. if (stat(G.crontab_dir_name, &sbuf) != 0)
  986. sbuf.st_mtime = 0; /* force update (once) if dir was deleted */
  987. if (G.crontab_dir_mtime != sbuf.st_mtime) {
  988. G.crontab_dir_mtime = sbuf.st_mtime;
  989. rescan = 1;
  990. }
  991. if (--rescan == 0) {
  992. rescan = 60;
  993. rescan_crontab_dir();
  994. }
  995. process_cron_update_file();
  996. log5("wakeup dt=%ld", dt);
  997. if (dt < -60 * 60 || dt > 60 * 60) {
  998. bb_info_msg("time disparity of %ld minutes detected", dt / 60);
  999. /* and we do not run any jobs in this case */
  1000. } else if (dt > 0) {
  1001. /* Usual case: time advances forward, as expected */
  1002. flag_starting_jobs(t1, t2);
  1003. start_jobs(START_ME_NORMAL);
  1004. sleep_time = 60;
  1005. if (check_completions() > 0) {
  1006. /* some jobs are still running */
  1007. sleep_time = 10;
  1008. }
  1009. }
  1010. /* else: time jumped back, do not run any jobs */
  1011. } /* for (;;) */
  1012. return 0; /* not reached */
  1013. }