crontab.c 9.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364
  1. /* crontab.c - files used to schedule the execution of programs.
  2. *
  3. * Copyright 2014 Ranjan Kumar <ranjankumar.bth@gmail.com>
  4. *
  5. * http://pubs.opengroup.org/onlinepubs/9699919799/utilities/crontab.html
  6. USE_CRONTAB(NEWTOY(crontab, "c:u:elr[!elr]", TOYFLAG_USR|TOYFLAG_BIN|TOYFLAG_STAYROOT))
  7. config CRONTAB
  8. bool "crontab"
  9. default n
  10. depends on TOYBOX_FORK
  11. help
  12. usage: crontab [-u user] FILE
  13. [-u user] [-e | -l | -r]
  14. [-c dir]
  15. Files used to schedule the execution of programs.
  16. -c crontab dir
  17. -e edit user's crontab
  18. -l list user's crontab
  19. -r delete user's crontab
  20. -u user
  21. FILE Replace crontab by FILE ('-': stdin)
  22. */
  23. #define FOR_crontab
  24. #include "toys.h"
  25. GLOBALS(
  26. char *user;
  27. char *cdir;
  28. )
  29. static char *omitspace(char *line)
  30. {
  31. while (*line == ' ' || *line == '\t') line++;
  32. return line;
  33. }
  34. /*
  35. * Names can also be used for the 'month' and 'day of week' fields
  36. * (First three letters of the particular day or month).
  37. */
  38. static int getindex(char *src, int size)
  39. {
  40. int i;
  41. char days[]={"sun""mon""tue""wed""thu""fri""sat"};
  42. char months[]={"jan""feb""mar""apr""may""jun""jul"
  43. "aug""sep""oct""nov""dec"};
  44. char *field = (size == 12) ? months : days;
  45. // strings are not allowed for min, hour and dom fields.
  46. if (!(size == 7 || size == 12)) return -1;
  47. for (i = 0; field[i]; i += 3) {
  48. if (!strncasecmp(src, &field[i], 3))
  49. return (i/3);
  50. }
  51. return -1;
  52. }
  53. static long getval(char *num, long low, long high)
  54. {
  55. long val = strtol(num, &num, 10);
  56. if (*num || (val < low) || (val > high)) return -1;
  57. return val;
  58. }
  59. // Validate minute, hour, day of month, month and day of week fields.
  60. static int validate_component(int min, int max, char *src)
  61. {
  62. int skip = 0;
  63. char *ptr;
  64. if (!src) return 1;
  65. if ((ptr = strchr(src, '/'))) {
  66. *ptr++ = 0;
  67. if ((skip = getval(ptr, min, (min ? max: max-1))) < 0) return 1;
  68. }
  69. if (*src == '-' || *src == ',') return 1;
  70. if (*src == '*') {
  71. if (*(src+1)) return 1;
  72. }
  73. else {
  74. for (;;) {
  75. char *ctoken = strsep(&src, ","), *dtoken;
  76. if (!ctoken) break;
  77. if (!*ctoken) return 1;
  78. // validate start position.
  79. dtoken = strsep(&ctoken, "-");
  80. if (isdigit(*dtoken)) {
  81. if (getval(dtoken, min, (min ? max : max-1)) < 0) return 1;
  82. } else if (getindex(dtoken, max) < 0) return 1;
  83. // validate end position.
  84. if (!ctoken) {
  85. if (skip) return 1; // case 10/20 or 1,2,4/3
  86. }
  87. else if (*ctoken) {// e.g. N-M
  88. if (isdigit(*ctoken)) {
  89. if (getval(ctoken, min, (min ? max : max-1)) < 0) return 1;
  90. } else if (getindex(ctoken, max) < 0) return 1;
  91. } else return 1; // error condition 'N-'
  92. }
  93. }
  94. return 0;
  95. }
  96. static int parse_crontab(char *fname)
  97. {
  98. FILE *fp = xfopen(fname, "r");
  99. long len = 0;
  100. char *line = NULL;
  101. size_t allocated_length;
  102. int lno;
  103. for (lno = 1; (len = getline(&line, &allocated_length, fp)) > 0; lno++) {
  104. char *name, *val, *tokens[5] = {0,}, *ptr = line;
  105. int count = 0;
  106. if (line[len - 1] == '\n') line[--len] = '\0';
  107. else {
  108. snprintf(toybuf, sizeof(toybuf), "'%d': premature EOF\n", lno);
  109. goto OUT;
  110. }
  111. ptr = omitspace(ptr);
  112. if (!*ptr || *ptr == '#' || *ptr == '@') continue;
  113. while (count<5) {
  114. int len = strcspn(ptr, " \t");
  115. if (ptr[len]) ptr[len++] = '\0';
  116. tokens[count++] = ptr;
  117. ptr += len;
  118. ptr = omitspace(ptr);
  119. if (!*ptr) break;
  120. }
  121. switch (count) {
  122. case 1: // form SHELL=/bin/sh
  123. name = tokens[0];
  124. if ((val = strchr(name, '='))) *val++ = 0;
  125. if (!val || !*val) {
  126. snprintf(toybuf, sizeof(toybuf), "'%d': %s\n", lno, line);
  127. goto OUT;
  128. }
  129. break;
  130. case 2: // form SHELL =/bin/sh or SHELL= /bin/sh
  131. name = tokens[0];
  132. if ((val = strchr(name, '='))) {
  133. *val = 0;
  134. val = tokens[1];
  135. } else {
  136. if (*(tokens[1]) != '=') {
  137. snprintf(toybuf, sizeof(toybuf), "'%d': %s\n", lno, line);
  138. goto OUT;
  139. }
  140. val = tokens[1] + 1;
  141. }
  142. if (!*val) {
  143. snprintf(toybuf, sizeof(toybuf), "'%d': %s\n", lno, line);
  144. goto OUT;
  145. }
  146. break;
  147. case 3: // NAME = VAL
  148. name = tokens[0];
  149. val = tokens[2];
  150. if (*(tokens[1]) != '=') {
  151. snprintf(toybuf, sizeof(toybuf), "'%d': %s\n", lno, line);
  152. goto OUT;
  153. }
  154. break;
  155. default:
  156. if (validate_component(0, 60, tokens[0])) {
  157. snprintf(toybuf, sizeof(toybuf), "'%d': bad minute\n", lno);
  158. goto OUT;
  159. }
  160. if (validate_component(0, 24, tokens[1])) {
  161. snprintf(toybuf, sizeof(toybuf), "'%d': bad hour\n", lno);
  162. goto OUT;
  163. }
  164. if (validate_component(1, 31, tokens[2])) {
  165. snprintf(toybuf, sizeof(toybuf), "'%d': bad day-of-month\n", lno);
  166. goto OUT;
  167. }
  168. if (validate_component(1, 12, tokens[3])) {
  169. snprintf(toybuf, sizeof(toybuf), "'%d': bad month\n", lno);
  170. goto OUT;
  171. }
  172. if (validate_component(0, 7, tokens[4])) {
  173. snprintf(toybuf, sizeof(toybuf), "'%d': bad day-of-week\n", lno);
  174. goto OUT;
  175. }
  176. if (!*ptr) { // don't have any cmd to execute.
  177. snprintf(toybuf, sizeof(toybuf), "'%d': bad command\n", lno);
  178. goto OUT;
  179. }
  180. break;
  181. }
  182. }
  183. free(line);
  184. fclose(fp);
  185. return 0;
  186. OUT:
  187. free(line);
  188. printf("Error at line no %s", toybuf);
  189. fclose(fp);
  190. return 1;
  191. }
  192. static void do_list(char *name)
  193. {
  194. int fdin;
  195. snprintf(toybuf, sizeof(toybuf), "%s%s", TT.cdir, name);
  196. fdin = xopenro(toybuf);
  197. xsendfile(fdin, 1);
  198. xclose(fdin);
  199. }
  200. static void do_remove(char *name)
  201. {
  202. snprintf(toybuf, sizeof(toybuf), "%s%s", TT.cdir, name);
  203. if (unlink(toybuf))
  204. error_exit("No crontab for '%s'", name);
  205. }
  206. static void update_crontab(char *src, char *dest)
  207. {
  208. int fdin, fdout;
  209. snprintf(toybuf, sizeof(toybuf), "%s%s", TT.cdir, dest);
  210. fdout = xcreate(toybuf, O_WRONLY|O_CREAT|O_TRUNC, 0600);
  211. fdin = xopenro(src);
  212. xsendfile(fdin, fdout);
  213. xclose(fdin);
  214. fchown(fdout, getuid(), geteuid());
  215. xclose(fdout);
  216. }
  217. static void do_replace(char *name)
  218. {
  219. char *fname = *toys.optargs ? *toys.optargs : "-";
  220. char tname[] = "/tmp/crontab.XXXXXX";
  221. if ((*fname == '-') && !*(fname+1)) {
  222. int tfd = mkstemp(tname);
  223. if (tfd < 0) perror_exit("mkstemp");
  224. xsendfile(0, tfd);
  225. xclose(tfd);
  226. fname = tname;
  227. }
  228. if (parse_crontab(fname))
  229. error_exit("errors in crontab file '%s', can't install.", fname);
  230. update_crontab(fname, name);
  231. unlink(tname);
  232. }
  233. static void do_edit(struct passwd *pwd)
  234. {
  235. struct stat sb;
  236. time_t mtime = 0;
  237. int srcfd, destfd, status;
  238. pid_t pid, cpid;
  239. char tname[] = "/tmp/crontab.XXXXXX";
  240. if ((destfd = mkstemp(tname)) < 0)
  241. perror_exit("Can't open tmp file");
  242. fchmod(destfd, 0666);
  243. snprintf(toybuf, sizeof(toybuf), "%s%s", TT.cdir, pwd->pw_name);
  244. if (!stat(toybuf, &sb)) { // file exists and have some content.
  245. if (sb.st_size) {
  246. srcfd = xopenro(toybuf);
  247. xsendfile(srcfd, destfd);
  248. xclose(srcfd);
  249. }
  250. } else printf("No crontab for '%s'- using an empty one\n", pwd->pw_name);
  251. xclose(destfd);
  252. if (!stat(tname, &sb)) mtime = sb.st_mtime;
  253. RETRY:
  254. if (!(pid = xfork())) {
  255. char *prog = pwd->pw_shell;
  256. xsetuser(pwd);
  257. if (pwd->pw_uid) {
  258. if (setenv("USER", pwd->pw_name, 1)) _exit(1);
  259. if (setenv("LOGNAME", pwd->pw_name, 1)) _exit(1);
  260. }
  261. if (setenv("HOME", pwd->pw_dir, 1)) _exit(1);
  262. if (setenv("SHELL",((!prog || !*prog) ? "/bin/sh" : prog), 1)) _exit(1);
  263. if (!(prog = getenv("VISUAL"))) {
  264. if (!(prog = getenv("EDITOR")))
  265. prog = "vi";
  266. }
  267. execlp(prog, prog, tname, (char *) NULL);
  268. perror_exit("can't execute '%s'", prog);
  269. }
  270. // Parent Process.
  271. do {
  272. cpid = waitpid(pid, &status, 0);
  273. } while ((cpid == -1) && (errno == EINTR));
  274. if (!stat(tname, &sb) && (mtime == sb.st_mtime)) {
  275. printf("%s: no changes made to crontab\n", toys.which->name);
  276. unlink(tname);
  277. return;
  278. }
  279. printf("%s: installing new crontab\n", toys.which->name);
  280. if (parse_crontab(tname)) {
  281. fprintf(stderr, "errors in crontab file, can't install.\n"
  282. "Do you want to retry the same edit? ");
  283. if (!yesno(0)) {
  284. error_msg("edits left in '%s'", tname);
  285. return;
  286. }
  287. goto RETRY;
  288. }
  289. // parsing of crontab success; update the crontab.
  290. update_crontab(tname, pwd->pw_name);
  291. unlink(tname);
  292. }
  293. void crontab_main(void)
  294. {
  295. struct passwd *pwd = NULL;
  296. long FLAG_elr = toys.optflags & (FLAG_e|FLAG_l|FLAG_r);
  297. if (TT.cdir && (TT.cdir[strlen(TT.cdir)-1] != '/'))
  298. TT.cdir = xmprintf("%s/", TT.cdir);
  299. if (!TT.cdir) TT.cdir = xstrdup("/var/spool/cron/crontabs/");
  300. if (toys.optflags & FLAG_u) {
  301. if (getuid()) error_exit("must be privileged to use -u");
  302. pwd = xgetpwnam(TT.user);
  303. } else pwd = xgetpwuid(getuid());
  304. if (!toys.optc) {
  305. if (!FLAG_elr) {
  306. if (toys.optflags & FLAG_u)
  307. help_exit("file name must be specified for replace");
  308. do_replace(pwd->pw_name);
  309. }
  310. else if (toys.optflags & FLAG_e) do_edit(pwd);
  311. else if (toys.optflags & FLAG_l) do_list(pwd->pw_name);
  312. else if (toys.optflags & FLAG_r) do_remove(pwd->pw_name);
  313. } else {
  314. if (FLAG_elr) help_exit("no arguments permitted after this option");
  315. do_replace(pwd->pw_name);
  316. }
  317. if (!(toys.optflags & FLAG_c)) free(TT.cdir);
  318. }