PageRenderTime 30ms CodeModel.GetById 5ms RepoModel.GetById 0ms app.codeStats 0ms

/usr.sbin/newsyslog/ptimes.c

https://bitbucket.org/freebsd/freebsd-head/
C | 618 lines | 373 code | 71 blank | 174 comment | 124 complexity | d3faf08ac71476195c2762e5e2738e92 MD5 | raw file
Possible License(s): MPL-2.0-no-copyleft-exception, BSD-3-Clause, LGPL-2.0, LGPL-2.1, BSD-2-Clause, 0BSD, JSON, AGPL-1.0, GPL-2.0
  1. /*-
  2. * ------+---------+---------+---------+---------+---------+---------+---------*
  3. * Initial version of parse8601 was originally added to newsyslog.c in
  4. * FreeBSD on Jan 22, 1999 by Garrett Wollman <wollman@FreeBSD.org>.
  5. * Initial version of parseDWM was originally added to newsyslog.c in
  6. * FreeBSD on Apr 4, 2000 by Hellmuth Michaelis <hm@FreeBSD.org>.
  7. *
  8. * Copyright (c) 2003 - Garance Alistair Drosehn <gad@FreeBSD.org>.
  9. * All rights reserved.
  10. *
  11. * Redistribution and use in source and binary forms, with or without
  12. * modification, are permitted provided that the following conditions
  13. * are met:
  14. * 1. Redistributions of source code must retain the above copyright
  15. * notice, this list of conditions and the following disclaimer.
  16. * 2. Redistributions in binary form must reproduce the above copyright
  17. * notice, this list of conditions and the following disclaimer in the
  18. * documentation and/or other materials provided with the distribution.
  19. *
  20. * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
  21. * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
  22. * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
  23. * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
  24. * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
  25. * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
  26. * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
  27. * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
  28. * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
  29. * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
  30. * SUCH DAMAGE.
  31. *
  32. * The views and conclusions contained in the software and documentation
  33. * are those of the authors and should not be interpreted as representing
  34. * official policies, either expressed or implied, of the FreeBSD Project.
  35. *
  36. * ------+---------+---------+---------+---------+---------+---------+---------*
  37. * This is intended to be a set of general-purpose routines to process times.
  38. * Right now it probably still has a number of assumptions in it, such that
  39. * it works fine for newsyslog but might not work for other uses.
  40. * ------+---------+---------+---------+---------+---------+---------+---------*
  41. */
  42. #include <sys/cdefs.h>
  43. __FBSDID("$FreeBSD$");
  44. #include <ctype.h>
  45. #include <limits.h>
  46. #include <stdio.h>
  47. #include <stdlib.h>
  48. #include <string.h>
  49. #include <time.h>
  50. #include "extern.h"
  51. #define SECS_PER_HOUR 3600
  52. /*
  53. * Bit-values which indicate which components of time were specified
  54. * by the string given to parse8601 or parseDWM. These are needed to
  55. * calculate what time-in-the-future will match that string.
  56. */
  57. #define TSPEC_YEAR 0x0001
  58. #define TSPEC_MONTHOFYEAR 0x0002
  59. #define TSPEC_LDAYOFMONTH 0x0004
  60. #define TSPEC_DAYOFMONTH 0x0008
  61. #define TSPEC_DAYOFWEEK 0x0010
  62. #define TSPEC_HOUROFDAY 0x0020
  63. #define TNYET_ADJ4DST -10 /* DST has "not yet" been adjusted */
  64. struct ptime_data {
  65. time_t basesecs; /* Base point for relative times */
  66. time_t tsecs; /* Time in seconds */
  67. struct tm basetm; /* Base Time expanded into fields */
  68. struct tm tm; /* Time expanded into fields */
  69. int did_adj4dst; /* Track calls to ptime_adjust4dst */
  70. int parseopts; /* Options given for parsing */
  71. int tmspec; /* Indicates which time fields had
  72. * been specified by the user */
  73. };
  74. static int days_pmonth(int month, int year);
  75. static int parse8601(struct ptime_data *ptime, const char *str);
  76. static int parseDWM(struct ptime_data *ptime, const char *str);
  77. /*
  78. * Simple routine to calculate the number of days in a given month.
  79. */
  80. static int
  81. days_pmonth(int month, int year)
  82. {
  83. static const int mtab[] = {31, 28, 31, 30, 31, 30, 31, 31,
  84. 30, 31, 30, 31};
  85. int ndays;
  86. ndays = mtab[month];
  87. if (month == 1) {
  88. /*
  89. * We are usually called with a 'tm-year' value
  90. * (ie, the value = the number of years past 1900).
  91. */
  92. if (year < 1900)
  93. year += 1900;
  94. if (year % 4 == 0) {
  95. /*
  96. * This is a leap year, as long as it is not a
  97. * multiple of 100, or if it is a multiple of
  98. * both 100 and 400.
  99. */
  100. if (year % 100 != 0)
  101. ndays++; /* not multiple of 100 */
  102. else if (year % 400 == 0)
  103. ndays++; /* is multiple of 100 and 400 */
  104. }
  105. }
  106. return (ndays);
  107. }
  108. /*-
  109. * Parse a limited subset of ISO 8601. The specific format is as follows:
  110. *
  111. * [CC[YY[MM[DD]]]][THH[MM[SS]]] (where `T' is the literal letter)
  112. *
  113. * We don't accept a timezone specification; missing fields (including timezone)
  114. * are defaulted to the current date but time zero.
  115. */
  116. static int
  117. parse8601(struct ptime_data *ptime, const char *s)
  118. {
  119. char *t;
  120. long l;
  121. struct tm tm;
  122. l = strtol(s, &t, 10);
  123. if (l < 0 || l >= INT_MAX || (*t != '\0' && *t != 'T'))
  124. return (-1);
  125. /*
  126. * Now t points either to the end of the string (if no time was
  127. * provided) or to the letter `T' which separates date and time in
  128. * ISO 8601. The pointer arithmetic is the same for either case.
  129. */
  130. tm = ptime->tm;
  131. ptime->tmspec = TSPEC_HOUROFDAY;
  132. switch (t - s) {
  133. case 8:
  134. tm.tm_year = ((l / 1000000) - 19) * 100;
  135. l = l % 1000000;
  136. case 6:
  137. ptime->tmspec |= TSPEC_YEAR;
  138. tm.tm_year -= tm.tm_year % 100;
  139. tm.tm_year += l / 10000;
  140. l = l % 10000;
  141. case 4:
  142. ptime->tmspec |= TSPEC_MONTHOFYEAR;
  143. tm.tm_mon = (l / 100) - 1;
  144. l = l % 100;
  145. case 2:
  146. ptime->tmspec |= TSPEC_DAYOFMONTH;
  147. tm.tm_mday = l;
  148. case 0:
  149. break;
  150. default:
  151. return (-1);
  152. }
  153. /* sanity check */
  154. if (tm.tm_year < 70 || tm.tm_mon < 0 || tm.tm_mon > 12
  155. || tm.tm_mday < 1 || tm.tm_mday > 31)
  156. return (-1);
  157. if (*t != '\0') {
  158. s = ++t;
  159. l = strtol(s, &t, 10);
  160. if (l < 0 || l >= INT_MAX || (*t != '\0' && !isspace(*t)))
  161. return (-1);
  162. switch (t - s) {
  163. case 6:
  164. tm.tm_sec = l % 100;
  165. l /= 100;
  166. case 4:
  167. tm.tm_min = l % 100;
  168. l /= 100;
  169. case 2:
  170. ptime->tmspec |= TSPEC_HOUROFDAY;
  171. tm.tm_hour = l;
  172. case 0:
  173. break;
  174. default:
  175. return (-1);
  176. }
  177. /* sanity check */
  178. if (tm.tm_sec < 0 || tm.tm_sec > 60 || tm.tm_min < 0
  179. || tm.tm_min > 59 || tm.tm_hour < 0 || tm.tm_hour > 23)
  180. return (-1);
  181. }
  182. ptime->tm = tm;
  183. return (0);
  184. }
  185. /*-
  186. * Parse a cyclic time specification, the format is as follows:
  187. *
  188. * [Dhh] or [Wd[Dhh]] or [Mdd[Dhh]]
  189. *
  190. * to rotate a logfile cyclic at
  191. *
  192. * - every day (D) within a specific hour (hh) (hh = 0...23)
  193. * - once a week (W) at a specific day (d) OR (d = 0..6, 0 = Sunday)
  194. * - once a month (M) at a specific day (d) (d = 1..31,l|L)
  195. *
  196. * We don't accept a timezone specification; missing fields
  197. * are defaulted to the current date but time zero.
  198. */
  199. static int
  200. parseDWM(struct ptime_data *ptime, const char *s)
  201. {
  202. int daysmon, Dseen, WMseen;
  203. const char *endval;
  204. char *tmp;
  205. long l;
  206. struct tm tm;
  207. /* Save away the number of days in this month */
  208. tm = ptime->tm;
  209. daysmon = days_pmonth(tm.tm_mon, tm.tm_year);
  210. WMseen = Dseen = 0;
  211. ptime->tmspec = TSPEC_HOUROFDAY;
  212. for (;;) {
  213. endval = NULL;
  214. switch (*s) {
  215. case 'D':
  216. if (Dseen)
  217. return (-1);
  218. Dseen++;
  219. ptime->tmspec |= TSPEC_HOUROFDAY;
  220. s++;
  221. l = strtol(s, &tmp, 10);
  222. if (l < 0 || l > 23)
  223. return (-1);
  224. endval = tmp;
  225. tm.tm_hour = l;
  226. break;
  227. case 'W':
  228. if (WMseen)
  229. return (-1);
  230. WMseen++;
  231. ptime->tmspec |= TSPEC_DAYOFWEEK;
  232. s++;
  233. l = strtol(s, &tmp, 10);
  234. if (l < 0 || l > 6)
  235. return (-1);
  236. endval = tmp;
  237. if (l != tm.tm_wday) {
  238. int save;
  239. if (l < tm.tm_wday) {
  240. save = 6 - tm.tm_wday;
  241. save += (l + 1);
  242. } else {
  243. save = l - tm.tm_wday;
  244. }
  245. tm.tm_mday += save;
  246. if (tm.tm_mday > daysmon) {
  247. tm.tm_mon++;
  248. tm.tm_mday = tm.tm_mday - daysmon;
  249. }
  250. }
  251. break;
  252. case 'M':
  253. if (WMseen)
  254. return (-1);
  255. WMseen++;
  256. ptime->tmspec |= TSPEC_DAYOFMONTH;
  257. s++;
  258. if (tolower(*s) == 'l') {
  259. /* User wants the last day of the month. */
  260. ptime->tmspec |= TSPEC_LDAYOFMONTH;
  261. tm.tm_mday = daysmon;
  262. endval = s + 1;
  263. } else {
  264. l = strtol(s, &tmp, 10);
  265. if (l < 1 || l > 31)
  266. return (-1);
  267. if (l > daysmon)
  268. return (-1);
  269. endval = tmp;
  270. tm.tm_mday = l;
  271. }
  272. break;
  273. default:
  274. return (-1);
  275. break;
  276. }
  277. if (endval == NULL)
  278. return (-1);
  279. else if (*endval == '\0' || isspace(*endval))
  280. break;
  281. else
  282. s = endval;
  283. }
  284. ptime->tm = tm;
  285. return (0);
  286. }
  287. /*
  288. * Initialize a new ptime-related data area.
  289. */
  290. struct ptime_data *
  291. ptime_init(const struct ptime_data *optsrc)
  292. {
  293. struct ptime_data *newdata;
  294. newdata = malloc(sizeof(struct ptime_data));
  295. if (optsrc != NULL) {
  296. memcpy(newdata, optsrc, sizeof(struct ptime_data));
  297. } else {
  298. memset(newdata, '\0', sizeof(struct ptime_data));
  299. newdata->did_adj4dst = TNYET_ADJ4DST;
  300. }
  301. return (newdata);
  302. }
  303. /*
  304. * Adjust a given time if that time is in a different timezone than
  305. * some other time.
  306. */
  307. int
  308. ptime_adjust4dst(struct ptime_data *ptime, const struct ptime_data *dstsrc)
  309. {
  310. struct ptime_data adjtime;
  311. if (ptime == NULL)
  312. return (-1);
  313. /*
  314. * Changes are not made to the given time until after all
  315. * of the calculations have been successful.
  316. */
  317. adjtime = *ptime;
  318. /* Check to see if this adjustment was already made */
  319. if ((adjtime.did_adj4dst != TNYET_ADJ4DST) &&
  320. (adjtime.did_adj4dst == dstsrc->tm.tm_isdst))
  321. return (0); /* yes, so don't make it twice */
  322. /* See if daylight-saving has changed between the two times. */
  323. if (dstsrc->tm.tm_isdst != adjtime.tm.tm_isdst) {
  324. if (adjtime.tm.tm_isdst == 1)
  325. adjtime.tsecs -= SECS_PER_HOUR;
  326. else if (adjtime.tm.tm_isdst == 0)
  327. adjtime.tsecs += SECS_PER_HOUR;
  328. adjtime.tm = *(localtime(&adjtime.tsecs));
  329. /* Remember that this adjustment has been made */
  330. adjtime.did_adj4dst = dstsrc->tm.tm_isdst;
  331. /*
  332. * XXX - Should probably check to see if changing the
  333. * hour also changed the value of is_dst. What
  334. * should we do in that case?
  335. */
  336. }
  337. *ptime = adjtime;
  338. return (0);
  339. }
  340. int
  341. ptime_relparse(struct ptime_data *ptime, int parseopts, time_t basetime,
  342. const char *str)
  343. {
  344. int dpm, pres;
  345. struct tm temp_tm;
  346. ptime->parseopts = parseopts;
  347. ptime->basesecs = basetime;
  348. ptime->basetm = *(localtime(&ptime->basesecs));
  349. ptime->tm = ptime->basetm;
  350. ptime->tm.tm_hour = ptime->tm.tm_min = ptime->tm.tm_sec = 0;
  351. /*
  352. * Call a routine which sets ptime.tm and ptime.tspecs based
  353. * on the given string and parsing-options. Note that the
  354. * routine should not call mktime to set ptime.tsecs.
  355. */
  356. if (parseopts & PTM_PARSE_DWM)
  357. pres = parseDWM(ptime, str);
  358. else
  359. pres = parse8601(ptime, str);
  360. if (pres < 0) {
  361. ptime->tsecs = (time_t)pres;
  362. return (pres);
  363. }
  364. /*
  365. * Before calling mktime, check to see if we ended up with a
  366. * "day-of-month" that does not exist in the selected month.
  367. * If we did call mktime with that info, then mktime will
  368. * make it look like the user specifically requested a day
  369. * in the following month (eg: Feb 31 turns into Mar 3rd).
  370. */
  371. dpm = days_pmonth(ptime->tm.tm_mon, ptime->tm.tm_year);
  372. if ((parseopts & PTM_PARSE_MATCHDOM) &&
  373. (ptime->tmspec & TSPEC_DAYOFMONTH) &&
  374. (ptime->tm.tm_mday> dpm)) {
  375. /*
  376. * ptime_nxtime() will want a ptime->tsecs value,
  377. * but we need to avoid mktime resetting all the
  378. * ptime->tm values.
  379. */
  380. if (verbose && dbg_at_times > 1)
  381. fprintf(stderr,
  382. "\t-- dom fixed: %4d/%02d/%02d %02d:%02d (%02d)",
  383. ptime->tm.tm_year, ptime->tm.tm_mon,
  384. ptime->tm.tm_mday, ptime->tm.tm_hour,
  385. ptime->tm.tm_min, dpm);
  386. temp_tm = ptime->tm;
  387. ptime->tsecs = mktime(&temp_tm);
  388. if (ptime->tsecs > (time_t)-1)
  389. ptimeset_nxtime(ptime);
  390. if (verbose && dbg_at_times > 1)
  391. fprintf(stderr,
  392. " to: %4d/%02d/%02d %02d:%02d\n",
  393. ptime->tm.tm_year, ptime->tm.tm_mon,
  394. ptime->tm.tm_mday, ptime->tm.tm_hour,
  395. ptime->tm.tm_min);
  396. }
  397. /*
  398. * Convert the ptime.tm into standard time_t seconds. Check
  399. * for invalid times, which includes things like the hour lost
  400. * when switching from "standard time" to "daylight saving".
  401. */
  402. ptime->tsecs = mktime(&ptime->tm);
  403. if (ptime->tsecs == (time_t)-1) {
  404. ptime->tsecs = (time_t)-2;
  405. return (-2);
  406. }
  407. return (0);
  408. }
  409. int
  410. ptime_free(struct ptime_data *ptime)
  411. {
  412. if (ptime == NULL)
  413. return (-1);
  414. free(ptime);
  415. return (0);
  416. }
  417. /*
  418. * Some trivial routines so ptime_data can remain a completely
  419. * opaque type.
  420. */
  421. const char *
  422. ptimeget_ctime(const struct ptime_data *ptime)
  423. {
  424. if (ptime == NULL)
  425. return ("Null time in ptimeget_ctime()\n");
  426. return (ctime(&ptime->tsecs));
  427. }
  428. double
  429. ptimeget_diff(const struct ptime_data *minuend, const struct
  430. ptime_data *subtrahend)
  431. {
  432. /* Just like difftime(), we have no good error-return */
  433. if (minuend == NULL || subtrahend == NULL)
  434. return (0.0);
  435. return (difftime(minuend->tsecs, subtrahend->tsecs));
  436. }
  437. time_t
  438. ptimeget_secs(const struct ptime_data *ptime)
  439. {
  440. if (ptime == NULL)
  441. return (-1);
  442. return (ptime->tsecs);
  443. }
  444. /*
  445. * Generate an approximate timestamp for the next event, based on
  446. * what parts of time were specified by the original parameter to
  447. * ptime_relparse(). The result may be -1 if there is no obvious
  448. * "next time" which will work.
  449. */
  450. int
  451. ptimeset_nxtime(struct ptime_data *ptime)
  452. {
  453. int moredays, tdpm, tmon, tyear;
  454. struct ptime_data nextmatch;
  455. if (ptime == NULL)
  456. return (-1);
  457. /*
  458. * Changes are not made to the given time until after all
  459. * of the calculations have been successful.
  460. */
  461. nextmatch = *ptime;
  462. /*
  463. * If the user specified a year and we're already past that
  464. * time, then there will never be another one!
  465. */
  466. if (ptime->tmspec & TSPEC_YEAR)
  467. return (-1);
  468. /*
  469. * The caller gave us a time in the past. Calculate how much
  470. * time is needed to go from that valid rotate time to the
  471. * next valid rotate time. We only need to get to the nearest
  472. * hour, because newsyslog is only run once per hour.
  473. */
  474. moredays = 0;
  475. if (ptime->tmspec & TSPEC_MONTHOFYEAR) {
  476. /* Special case: Feb 29th does not happen every year. */
  477. if (ptime->tm.tm_mon == 1 && ptime->tm.tm_mday == 29) {
  478. nextmatch.tm.tm_year += 4;
  479. if (days_pmonth(1, nextmatch.tm.tm_year) < 29)
  480. nextmatch.tm.tm_year += 4;
  481. } else {
  482. nextmatch.tm.tm_year += 1;
  483. }
  484. nextmatch.tm.tm_isdst = -1;
  485. nextmatch.tsecs = mktime(&nextmatch.tm);
  486. } else if (ptime->tmspec & TSPEC_LDAYOFMONTH) {
  487. /*
  488. * Need to get to the last day of next month. Origtm is
  489. * already at the last day of this month, so just add to
  490. * it number of days in the next month.
  491. */
  492. if (ptime->tm.tm_mon < 11)
  493. moredays = days_pmonth(ptime->tm.tm_mon + 1,
  494. ptime->tm.tm_year);
  495. else
  496. moredays = days_pmonth(0, ptime->tm.tm_year + 1);
  497. } else if (ptime->tmspec & TSPEC_DAYOFMONTH) {
  498. /* Jump to the same day in the next month */
  499. moredays = days_pmonth(ptime->tm.tm_mon, ptime->tm.tm_year);
  500. /*
  501. * In some cases, the next month may not *have* the
  502. * desired day-of-the-month. If that happens, then
  503. * move to the next month that does have enough days.
  504. */
  505. tmon = ptime->tm.tm_mon;
  506. tyear = ptime->tm.tm_year;
  507. for (;;) {
  508. if (tmon < 11)
  509. tmon += 1;
  510. else {
  511. tmon = 0;
  512. tyear += 1;
  513. }
  514. tdpm = days_pmonth(tmon, tyear);
  515. if (tdpm >= ptime->tm.tm_mday)
  516. break;
  517. moredays += tdpm;
  518. }
  519. } else if (ptime->tmspec & TSPEC_DAYOFWEEK) {
  520. moredays = 7;
  521. } else if (ptime->tmspec & TSPEC_HOUROFDAY) {
  522. moredays = 1;
  523. }
  524. if (moredays != 0) {
  525. nextmatch.tsecs += SECS_PER_HOUR * 24 * moredays;
  526. nextmatch.tm = *(localtime(&nextmatch.tsecs));
  527. }
  528. /*
  529. * The new time will need to be adjusted if the setting of
  530. * daylight-saving has changed between the two times.
  531. */
  532. ptime_adjust4dst(&nextmatch, ptime);
  533. /* Everything worked. Update the given time and return. */
  534. *ptime = nextmatch;
  535. return (0);
  536. }
  537. int
  538. ptimeset_time(struct ptime_data *ptime, time_t secs)
  539. {
  540. if (ptime == NULL)
  541. return (-1);
  542. ptime->tsecs = secs;
  543. ptime->tm = *(localtime(&ptime->tsecs));
  544. ptime->parseopts = 0;
  545. /* ptime->tmspec = ? */
  546. return (0);
  547. }