0.9.8.10
 All Classes Namespaces Files Functions Variables Typedefs Enumerations Enumerator Friends Macros Modules Pages
Crontab.cc
Go to the documentation of this file.
1 /*
2  * Copyright (C) 2007-2015 Hypertable, Inc.
3  *
4  * This file is part of Hypertable.
5  *
6  * Hypertable is free software; you can redistribute it and/or
7  * modify it under the terms of the GNU General Public License
8  * as published by the Free Software Foundation; either version 3
9  * of the License, or any later version.
10  *
11  * Hypertable is distributed in the hope that it will be useful,
12  * but WITHOUT ANY WARRANTY; without even the implied warranty of
13  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14  * GNU General Public License for more details.
15  *
16  * You should have received a copy of the GNU General Public License
17  * along with this program; if not, write to the Free Software
18  * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
19  * 02110-1301, USA.
20  */
21 
26 
27 #include <Common/Compat.h>
28 #include "Crontab.h"
29 
30 #include <Common/Logger.h>
31 #include <Common/StringExt.h>
32 
33 #include <boost/tokenizer.hpp>
34 
35 #include <cctype>
36 #include <cstdlib>
37 
38 using namespace Hypertable;
39 using namespace boost;
40 using namespace std;
41 
42 Crontab::Crontab(const String &spec) {
43  parse_entry(spec, &m_entry);
44 }
45 
46 time_t Crontab::next_event(time_t now) {
47  struct tm next_tm;
48  int i;
49  int hour_increment = 0;
50  int day_increment = 0;
51 
52  // align to "next" minute boundary
53  now += 59;
54 
55  localtime_r(&now, &next_tm);
56  next_tm.tm_sec = 0;
57 
58  next_day:
59  next_matching_day(&next_tm, day_increment);
60 
61  next_hour:
62  for (i = next_tm.tm_hour + hour_increment; i < 24; i++) {
63  if (m_entry.hour[i]) {
64  if (i > next_tm.tm_hour) {
65  next_tm.tm_hour = i;
66  next_tm.tm_min = 0;
67  }
68  break;
69  }
70  }
71  if (i == 24) {
72  day_increment = 1;
73  hour_increment = 0;
74  goto next_day;
75  }
76 
77  for (i = next_tm.tm_min; i < 60; i++) {
78  if (m_entry.minute[i]) {
79  next_tm.tm_min = i;
80  return mktime(&next_tm);
81  }
82  }
83  if (i == 60) {
84  hour_increment = 1;
85  next_tm.tm_min = 0;
86  goto next_hour;
87  }
88 
89  return mktime(&next_tm);
90 }
91 
92 void Crontab::next_matching_day(struct tm *next_tm, bool increment) {
93  time_t t;
94  bool advanced = false;
95 
96  if (increment) {
97  next_tm->tm_sec = 0;
98  next_tm->tm_min = 0;
99  next_tm->tm_hour = 0;
100  t = mktime(next_tm) + 86400;
101  localtime_r(&t, next_tm);
102  advanced = true;
103  }
104 
105  while (true) {
106  if (m_entry.month[next_tm->tm_mon] &&
107  (m_entry.dom[(next_tm->tm_mday - 1)] || m_entry.dow[next_tm->tm_wday]))
108  break;
109  t = mktime(next_tm) + 86400;
110  localtime_r(&t, next_tm);
111  advanced = true;
112  }
113  if (advanced) {
114  next_tm->tm_sec = 0;
115  next_tm->tm_min = 0;
116  next_tm->tm_hour = 0;
117  }
118 }
119 
120 
121 void Crontab::parse_entry(const String &spec, crontab_entry *entry) {
122  String text = spec;
123  String fields[5];
124  size_t field_count = 0;
125  bool wildcard_dom = false;
126  bool wildcard_dow = false;
127 
128  {
129  char_separator<char> sep(" ");
130  tokenizer< char_separator<char> > tokens(text, sep);
131  for (const auto &t : tokens) {
132  if (field_count == 5)
133  HT_THROW(Error::COMMAND_PARSE_ERROR, "too many fields");
134  fields[field_count++] = t;
135  }
136  if (field_count < 5)
137  HT_THROW(Error::COMMAND_PARSE_ERROR, "too few fields");
138  }
139 
140  // minute
141  {
142  char_separator<char> sep(",");
143  tokenizer< char_separator<char> > tokens(fields[0], sep);
144  for (const auto &t : tokens)
145  parse_range<60>(t, entry->minute);
146  }
147 
148  // hour
149  {
150  char_separator<char> sep(",");
151  tokenizer< char_separator<char> > tokens(fields[1], sep);
152  for (const auto &t : tokens)
153  parse_range<24>(t, entry->hour);
154  }
155 
156  // dom
157  {
158  if (fields[2] == "*")
159  wildcard_dom = true;
160  else {
161  char_separator<char> sep(",");
162  tokenizer< char_separator<char> > tokens(fields[2], sep);
163  for (const auto &t : tokens)
164  parse_range<31>(t, entry->dom, false);
165  }
166  }
167 
168  // month
169  {
170  char_separator<char> sep(",");
171  tokenizer< char_separator<char> > tokens(fields[3], sep);
172  for (const auto &t : tokens)
173  parse_range<12>(t, entry->month, false);
174  }
175 
176  // dow
177  {
178  if (fields[4] == "*")
179  wildcard_dow = true;
180  else {
181  char_separator<char> sep(",");
182  tokenizer< char_separator<char> > tokens(fields[4], sep);
183  for (const auto &t : tokens)
184  parse_range<8>(t, entry->dow);
185  if (entry->dow[7])
186  entry->dow[0] = true;
187  if (entry->dow[0])
188  entry->dow[7] = true;
189  }
190  }
191 
192  if (wildcard_dom) {
193  if (wildcard_dow)
194  entry->dom.set();
195  else
196  entry->dom.reset();
197  }
198  else {
199  if (wildcard_dow)
200  entry->dow.reset();
201  }
202 
203 }
204 
205 template <int N>
206 void Crontab::parse_range(const String &spec, std::bitset<N> &bits,
207  bool zero_based) {
208  String text = spec;
209  String range, step_str;
210  size_t count = 0;
211 
212  // Split into range & step
213  int step = -1;
214  {
215  char_separator<char> sep("/");
216  tokenizer< char_separator<char> > tokens(text, sep);
217  for (const auto &t : tokens) {
218  if (count == 0)
219  range = t;
220  else if (count == 1)
221  step_str = t;
222  else
223  HT_THROW(Error::COMMAND_PARSE_ERROR, "bad specification");
224  count++;
225  }
226 
227  if (count == 2) {
228  if (step_str == "")
229  HT_THROW(Error::COMMAND_PARSE_ERROR, "bad specification");
230  for (const char *ptr = step_str.c_str(); *ptr; ptr++) {
231  if (!isdigit(*ptr))
232  HT_THROW(Error::COMMAND_PARSE_ERROR, "bad specification");
233  }
234  step = atoi(step_str.c_str());
235  }
236  }
237 
238  count = 0;
239  int start = -1;
240  int end = -1;
241  bool got_wildcard = false;
242  {
243  char_separator<char> sep("-");
244  tokenizer< char_separator<char> > tokens(range, sep);
245  for (const auto &t : tokens) {
246  if (count == 0) {
247  if (t == "*") {
248  bits.set();
249  got_wildcard = true;
250  }
251  else {
252  for (const char *ptr = t.c_str(); *ptr; ptr++) {
253  if (!isdigit(*ptr))
254  HT_THROW(Error::COMMAND_PARSE_ERROR, "bad specification");
255  }
256  start = atoi(t.c_str());
257  }
258  }
259  else if (count == 1) {
260  if (got_wildcard)
261  HT_THROW(Error::COMMAND_PARSE_ERROR, "bad specification");
262  for (const char *ptr = t.c_str(); *ptr; ptr++) {
263  if (!isdigit(*ptr))
264  HT_THROW(Error::COMMAND_PARSE_ERROR, "bad specification");
265  }
266  end = atoi(t.c_str());
267  }
268  else
269  HT_THROW(Error::COMMAND_PARSE_ERROR, "bad specification");
270  count++;
271  }
272  }
273 
274  if (got_wildcard)
275  return;
276 
277  int offset = 0;
278  if (!zero_based) {
279  offset = 1;
280  if (start == 0)
281  HT_THROW(Error::COMMAND_PARSE_ERROR, "bad specification");
282  }
283 
284  if (start == -1)
285  HT_THROW(Error::COMMAND_PARSE_ERROR, "bad specification");
286 
287  if (start >= (N+offset) || end >= (N+offset))
288  HT_THROW(Error::COMMAND_PARSE_ERROR, "bad specification");
289 
290 
291  if (end == -1)
292  bits[start-offset] = true;
293 
294  if (step == -1)
295  step = 1;
296 
297  for (int i=(start-offset); i<=(end-offset); i+=step)
298  bits[i] = true;
299 }
300 
301 std::ostream &Hypertable::operator<<(std::ostream &os,
302  const Hypertable::crontab_entry &entry) {
303  String spec;
304 
305  reconstruct_spec<60>(entry.minute, spec);
306  os << spec;
307  reconstruct_spec<24>(entry.hour, spec);
308  os << " " << spec;
309  reconstruct_spec<31>(entry.dom, spec);
310  os << " " << spec;
311  reconstruct_spec<12>(entry.month, spec);
312  os << " " << spec;
313  reconstruct_spec<8>(entry.dow, spec);
314  os << " " << spec;
315 
316  return os;
317 }
318 
319 template <int N> void Hypertable::reconstruct_spec(const std::bitset<N> &bits,
320  String &spec) {
321  bool first = true;
322  int start = -1;
323  int end = -1;
324  spec = "";
325  for (int i=0; i<N; i++) {
326  if (bits[i]) {
327  if (start == -1)
328  start = i;
329  else
330  end = i;
331  }
332  else if (start != -1) {
333  if (!first)
334  spec += ",";
335  first = false;
336  if (i == start+1)
337  spec += String("") + start;
338  else
339  spec += String("") + start + "-" + end;
340  start = end = -1;
341  }
342  }
343  if (start != -1) {
344  if (!first)
345  spec += ",";
346  if (start == 0 && end == N-1)
347  spec += "*";
348  else {
349  if (end == -1)
350  spec += String("") + start;
351  else
352  spec += String("") + start + "-" + end;
353  }
354  }
355  else if (first)
356  spec += "*";
357 }
time_t next_event(time_t now)
Retrieves the timestamp of the next event.
Definition: Crontab.cc:46
Boost library.
Definition: Properties.cc:39
std::string String
A String is simply a typedef to std::string.
Definition: String.h:44
std::bitset< 24 > hour
Definition: Crontab.h:43
std::bitset< 31 > dom
Definition: Crontab.h:44
STL namespace.
void parse_entry(const String &spec, crontab_entry *_entry)
Parses a crontab spec into a crontab_entry.
Definition: Crontab.cc:121
Binary representation of crontab spec.
Definition: Crontab.h:41
Logging routines and macros.
std::bitset< 12 > month
Definition: Crontab.h:45
Compatibility Macros for C/C++.
std::ostream & operator<<(std::ostream &os, const crontab_entry &entry)
Helper function to write crontab_entry to an ostream.
Definition: Crontab.cc:301
Hypertable definitions
void parse_range(const String &spec, std::bitset< N > &bits, bool zero_based=true)
Parses a crontab field and sets corresponding bits in bits.
Definition: Crontab.cc:206
Crontab()
Default constructor.
Definition: Crontab.h:59
Crontab class for periodic events.
void next_matching_day(struct tm *next_tm, bool increment)
Determines next day on which event will occur.
Definition: Crontab.cc:92
void reconstruct_spec(const std::bitset< N > &bits, String &spec)
Converts binary crontab spec back into string spec.
Definition: Crontab.cc:319
String extensions and helpers: sets, maps, append operators etc.
#define HT_THROW(_code_, _msg_)
Definition: Error.h:478
std::bitset< 60 > minute
Definition: Crontab.h:42
std::bitset< 8 > dow
Definition: Crontab.h:46