]> granicus.if.org Git - icinga2/blob - lib/icinga/legacytimeperiod.cpp
Merge pull request #7000 from Icinga/bugfix/goto-loop
[icinga2] / lib / icinga / legacytimeperiod.cpp
1 /* Icinga 2 | (c) 2012 Icinga GmbH | GPLv2+ */
2
3 #include "icinga/legacytimeperiod.hpp"
4 #include "base/function.hpp"
5 #include "base/convert.hpp"
6 #include "base/exception.hpp"
7 #include "base/objectlock.hpp"
8 #include "base/logger.hpp"
9 #include "base/debug.hpp"
10 #include "base/utility.hpp"
11
12 using namespace icinga;
13
14 REGISTER_FUNCTION_NONCONST(Internal, LegacyTimePeriod, &LegacyTimePeriod::ScriptFunc, "tp:begin:end");
15
16 bool LegacyTimePeriod::IsInTimeRange(tm *begin, tm *end, int stride, tm *reference)
17 {
18         time_t tsbegin, tsend, tsref;
19         tsbegin = mktime(begin);
20         tsend = mktime(end);
21         tsref = mktime(reference);
22
23         if (tsref < tsbegin || tsref > tsend)
24                 return false;
25
26         int daynumber = (tsref - tsbegin) / (24 * 60 * 60);
27
28         if (stride > 1 && daynumber % stride > 0)
29                 return false;
30
31         return true;
32 }
33
34 void LegacyTimePeriod::FindNthWeekday(int wday, int n, tm *reference)
35 {
36         int dir, seen = 0;
37
38         if (n > 0) {
39                 dir = 1;
40         } else {
41                 n *= -1;
42                 dir = -1;
43
44                 /* Negative days are relative to the next month. */
45                 reference->tm_mon++;
46         }
47
48         ASSERT(n > 0);
49
50         reference->tm_mday = 1;
51
52         for (;;) {
53                 mktime(reference);
54
55                 if (reference->tm_wday == wday) {
56                         seen++;
57
58                         if (seen == n)
59                                 return;
60                 }
61
62                 reference->tm_mday += dir;
63         }
64 }
65
66 int LegacyTimePeriod::WeekdayFromString(const String& daydef)
67 {
68         if (daydef == "sunday")
69                 return 0;
70         else if (daydef == "monday")
71                 return 1;
72         else if (daydef == "tuesday")
73                 return 2;
74         else if (daydef == "wednesday")
75                 return 3;
76         else if (daydef == "thursday")
77                 return 4;
78         else if (daydef == "friday")
79                 return 5;
80         else if (daydef == "saturday")
81                 return 6;
82         else
83                 return -1;
84 }
85
86 int LegacyTimePeriod::MonthFromString(const String& monthdef)
87 {
88         if (monthdef == "january")
89                 return 0;
90         else if (monthdef == "february")
91                 return 1;
92         else if (monthdef == "march")
93                 return 2;
94         else if (monthdef == "april")
95                 return 3;
96         else if (monthdef == "may")
97                 return 4;
98         else if (monthdef == "june")
99                 return 5;
100         else if (monthdef == "july")
101                 return 6;
102         else if (monthdef == "august")
103                 return 7;
104         else if (monthdef == "september")
105                 return 8;
106         else if (monthdef == "october")
107                 return 9;
108         else if (monthdef == "november")
109                 return 10;
110         else if (monthdef == "december")
111                 return 11;
112         else
113                 return -1;
114 }
115
116 void LegacyTimePeriod::ParseTimeSpec(const String& timespec, tm *begin, tm *end, tm *reference)
117 {
118         /* Let mktime() figure out whether we're in DST or not. */
119         reference->tm_isdst = -1;
120
121         /* YYYY-MM-DD */
122         if (timespec.GetLength() == 10 && timespec[4] == '-' && timespec[7] == '-') {
123                 int year = Convert::ToLong(timespec.SubStr(0, 4));
124                 int month = Convert::ToLong(timespec.SubStr(5, 2));
125                 int day = Convert::ToLong(timespec.SubStr(8, 2));
126
127                 if (month < 1 || month > 12)
128                         BOOST_THROW_EXCEPTION(std::invalid_argument("Invalid month in time specification: " + timespec));
129                 if (day < 1 || day > 31)
130                         BOOST_THROW_EXCEPTION(std::invalid_argument("Invalid day in time specification: " + timespec));
131
132                 if (begin) {
133                         *begin = *reference;
134                         begin->tm_year = year - 1900;
135                         begin->tm_mon = month - 1;
136                         begin->tm_mday = day;
137                         begin->tm_hour = 0;
138                         begin->tm_min = 0;
139                         begin->tm_sec = 0;
140                 }
141
142                 if (end) {
143                         *end = *reference;
144                         end->tm_year = year - 1900;
145                         end->tm_mon = month - 1;
146                         end->tm_mday = day;
147                         end->tm_hour = 24;
148                         end->tm_min = 0;
149                         end->tm_sec = 0;
150                 }
151
152                 return;
153         }
154
155         std::vector<String> tokens = timespec.Split(" ");
156
157         int mon = -1;
158
159         if (tokens.size() > 1 && (tokens[0] == "day" || (mon = MonthFromString(tokens[0])) != -1)) {
160                 if (mon == -1)
161                         mon = reference->tm_mon;
162
163                 int mday = Convert::ToLong(tokens[1]);
164
165                 if (begin) {
166                         *begin = *reference;
167                         begin->tm_mon = mon;
168                         begin->tm_mday = mday;
169                         begin->tm_hour = 0;
170                         begin->tm_min = 0;
171                         begin->tm_sec = 0;
172
173                         /* Negative days are relative to the next month. */
174                         if (mday < 0) {
175                                 begin->tm_mday = mday * -1 - 1;
176                                 begin->tm_mon++;
177                         }
178                 }
179
180                 if (end) {
181                         *end = *reference;
182                         end->tm_mon = mon;
183                         end->tm_mday = mday;
184                         end->tm_hour = 24;
185                         end->tm_min = 0;
186                         end->tm_sec = 0;
187
188                         /* Negative days are relative to the next month. */
189                         if (mday < 0) {
190                                 end->tm_mday = mday * -1 - 1;
191                                 end->tm_mon++;
192                         }
193                 }
194
195                 return;
196         }
197
198         int wday;
199
200         if (tokens.size() >= 1 && (wday = WeekdayFromString(tokens[0])) != -1) {
201                 tm myref = *reference;
202
203                 if (tokens.size() > 2) {
204                         mon = MonthFromString(tokens[2]);
205
206                         if (mon == -1)
207                                 BOOST_THROW_EXCEPTION(std::invalid_argument("Invalid month in time specification: " + timespec));
208
209                         myref.tm_mon = mon;
210                 }
211
212                 int n = 0;
213
214                 if (tokens.size() > 1)
215                         n = Convert::ToLong(tokens[1]);
216
217                 if (begin) {
218                         *begin = myref;
219
220                         if (tokens.size() > 1)
221                                 FindNthWeekday(wday, n, begin);
222                         else
223                                 begin->tm_mday += (7 - begin->tm_wday + wday) % 7;
224
225                         begin->tm_hour = 0;
226                         begin->tm_min = 0;
227                         begin->tm_sec = 0;
228                 }
229
230                 if (end) {
231                         *end = myref;
232
233                         if (tokens.size() > 1)
234                                 FindNthWeekday(wday, n, end);
235                         else
236                                 end->tm_mday += (7 - end->tm_wday + wday) % 7;
237
238                         end->tm_hour = 0;
239                         end->tm_min = 0;
240                         end->tm_sec = 0;
241                         end->tm_mday++;
242                 }
243
244                 return;
245         }
246
247         BOOST_THROW_EXCEPTION(std::invalid_argument("Invalid time specification: " + timespec));
248 }
249
250 void LegacyTimePeriod::ParseTimeRange(const String& timerange, tm *begin, tm *end, int *stride, tm *reference)
251 {
252         String def = timerange;
253
254         /* Figure out the stride. */
255         size_t pos = def.FindFirstOf('/');
256
257         if (pos != String::NPos) {
258                 String strStride = def.SubStr(pos + 1).Trim();
259                 *stride = Convert::ToLong(strStride);
260
261                 /* Remove the stride parameter from the definition. */
262                 def = def.SubStr(0, pos);
263         } else {
264                 *stride = 1; /* User didn't specify anything, assume default. */
265         }
266
267         /* Figure out whether the user has specified two dates. */
268         pos = def.Find("- ");
269
270         if (pos != String::NPos) {
271                 String first = def.SubStr(0, pos).Trim();
272
273                 String second = def.SubStr(pos + 1).Trim();
274
275                 ParseTimeSpec(first, begin, nullptr, reference);
276
277                 /* If the second definition starts with a number we need
278                  * to add the first word from the first definition, e.g.:
279                  * day 1 - 15 --> "day 15" */
280                 bool is_number = true;
281                 size_t xpos = second.FindFirstOf(' ');
282                 String fword = second.SubStr(0, xpos);
283
284                 try {
285                         Convert::ToLong(fword);
286                 } catch (...) {
287                         is_number = false;
288                 }
289
290                 if (is_number) {
291                         xpos = first.FindFirstOf(' ');
292                         ASSERT(xpos != String::NPos);
293                         second = first.SubStr(0, xpos + 1) + second;
294                 }
295
296                 ParseTimeSpec(second, nullptr, end, reference);
297         } else {
298                 ParseTimeSpec(def, begin, end, reference);
299         }
300 }
301
302 bool LegacyTimePeriod::IsInDayDefinition(const String& daydef, tm *reference)
303 {
304         tm begin, end;
305         int stride;
306
307         ParseTimeRange(daydef, &begin, &end, &stride, reference);
308
309         Log(LogDebug, "LegacyTimePeriod")
310                 << "ParseTimeRange: '" << daydef << "' => " << mktime(&begin)
311                 << " -> " << mktime(&end) << ", stride: " << stride;
312
313         return IsInTimeRange(&begin, &end, stride, reference);
314 }
315
316 void LegacyTimePeriod::ProcessTimeRangeRaw(const String& timerange, tm *reference, tm *begin, tm *end)
317 {
318         std::vector<String> times = timerange.Split("-");
319
320         if (times.size() != 2)
321                 BOOST_THROW_EXCEPTION(std::invalid_argument("Invalid timerange: " + timerange));
322
323         std::vector<String> hd1 = times[0].Split(":");
324
325         if (hd1.size() != 2)
326                 BOOST_THROW_EXCEPTION(std::invalid_argument("Invalid time specification: " + times[0]));
327
328         std::vector<String> hd2 = times[1].Split(":");
329
330         if (hd2.size() != 2)
331                 BOOST_THROW_EXCEPTION(std::invalid_argument("Invalid time specification: " + times[1]));
332
333         *begin = *reference;
334         begin->tm_sec = 0;
335         begin->tm_min = Convert::ToLong(hd1[1]);
336         begin->tm_hour = Convert::ToLong(hd1[0]);
337
338         *end = *reference;
339         end->tm_sec = 0;
340         end->tm_min = Convert::ToLong(hd2[1]);
341         end->tm_hour = Convert::ToLong(hd2[0]);
342
343         if (begin->tm_hour * 3600 + begin->tm_min * 60 + begin->tm_sec >=
344                 end->tm_hour * 3600 + end->tm_min * 60 + end->tm_sec)
345                 BOOST_THROW_EXCEPTION(std::invalid_argument("Time period segment ends before it begins"));
346 }
347
348 Dictionary::Ptr LegacyTimePeriod::ProcessTimeRange(const String& timestamp, tm *reference)
349 {
350         tm begin, end;
351
352         ProcessTimeRangeRaw(timestamp, reference, &begin, &end);
353
354         return new Dictionary({
355                 { "begin", (long)mktime(&begin) },
356                 { "end", (long)mktime(&end) }
357         });
358 }
359
360 void LegacyTimePeriod::ProcessTimeRanges(const String& timeranges, tm *reference, const Array::Ptr& result)
361 {
362         std::vector<String> ranges = timeranges.Split(",");
363
364         for (const String& range : ranges) {
365                 Dictionary::Ptr segment = ProcessTimeRange(range, reference);
366
367                 if (segment->Get("begin") >= segment->Get("end"))
368                         continue;
369
370                 result->Add(segment);
371         }
372 }
373
374 Dictionary::Ptr LegacyTimePeriod::FindRunningSegment(const String& daydef, const String& timeranges, tm *reference)
375 {
376         tm begin, end, iter;
377         time_t tsend, tsiter, tsref;
378         int stride;
379
380         tsref = mktime(reference);
381
382         ParseTimeRange(daydef, &begin, &end, &stride, reference);
383
384         iter = begin;
385
386         tsend = mktime(&end);
387
388         do {
389                 if (IsInTimeRange(&begin, &end, stride, &iter)) {
390                         Array::Ptr segments = new Array();
391                         ProcessTimeRanges(timeranges, &iter, segments);
392
393                         Dictionary::Ptr bestSegment;
394                         double bestEnd = 0.0;
395
396                         ObjectLock olock(segments);
397                         for (const Dictionary::Ptr& segment : segments) {
398                                 double begin = segment->Get("begin");
399                                 double end = segment->Get("end");
400
401                                 if (begin >= tsref || end < tsref)
402                                         continue;
403
404                                 if (!bestSegment || end > bestEnd) {
405                                         bestSegment = segment;
406                                         bestEnd = end;
407                                 }
408                         }
409
410                         if (bestSegment)
411                                 return bestSegment;
412                 }
413
414                 iter.tm_mday++;
415                 iter.tm_hour = 0;
416                 iter.tm_min = 0;
417                 iter.tm_sec = 0;
418                 tsiter = mktime(&iter);
419         } while (tsiter < tsend);
420
421         return nullptr;
422 }
423
424 Dictionary::Ptr LegacyTimePeriod::FindNextSegment(const String& daydef, const String& timeranges, tm *reference)
425 {
426         tm begin, end, iter, ref;
427         time_t tsend, tsiter, tsref;
428         int stride;
429
430         for (int pass = 1; pass <= 2; pass++) {
431                 if (pass == 1) {
432                         ref = *reference;
433                 } else {
434                         ref = end;
435                         ref.tm_mday++;
436                 }
437
438                 tsref = mktime(&ref);
439
440                 ParseTimeRange(daydef, &begin, &end, &stride, &ref);
441
442                 iter = begin;
443
444                 tsend = mktime(&end);
445
446                 do {
447                         if (IsInTimeRange(&begin, &end, stride, &iter)) {
448                                 Array::Ptr segments = new Array();
449                                 ProcessTimeRanges(timeranges, &iter, segments);
450
451                                 Dictionary::Ptr bestSegment;
452                                 double bestBegin;
453
454                                 ObjectLock olock(segments);
455                                 for (const Dictionary::Ptr& segment : segments) {
456                                         double begin = segment->Get("begin");
457
458                                         if (begin < tsref)
459                                                 continue;
460
461                                         if (!bestSegment || begin < bestBegin) {
462                                                 bestSegment = segment;
463                                                 bestBegin = begin;
464                                         }
465                                 }
466
467                                 if (bestSegment)
468                                         return bestSegment;
469                         }
470
471                         iter.tm_mday++;
472                         iter.tm_hour = 0;
473                         iter.tm_min = 0;
474                         iter.tm_sec = 0;
475                         tsiter = mktime(&iter);
476                 } while (tsiter < tsend);
477         }
478
479         return nullptr;
480 }
481
482 Array::Ptr LegacyTimePeriod::ScriptFunc(const TimePeriod::Ptr& tp, double begin, double end)
483 {
484         Array::Ptr segments = new Array();
485
486         Dictionary::Ptr ranges = tp->GetRanges();
487
488         if (ranges) {
489                 for (int i = 0; i <= (end - begin) / (24 * 60 * 60); i++) {
490                         time_t refts = begin + i * 24 * 60 * 60;
491                         tm reference = Utility::LocalTime(refts);
492
493 #ifdef I2_DEBUG
494                         Log(LogDebug, "LegacyTimePeriod")
495                                 << "Checking reference time " << refts;
496 #endif /* I2_DEBUG */
497
498                         ObjectLock olock(ranges);
499                         for (const Dictionary::Pair& kv : ranges) {
500                                 if (!IsInDayDefinition(kv.first, &reference)) {
501 #ifdef I2_DEBUG
502                                         Log(LogDebug, "LegacyTimePeriod")
503                                                 << "Not in day definition '" << kv.first << "'.";
504 #endif /* I2_DEBUG */
505                                         continue;
506                                 }
507
508 #ifdef I2_DEBUG
509                                 Log(LogDebug, "LegacyTimePeriod")
510                                         << "In day definition '" << kv.first << "'.";
511 #endif /* I2_DEBUG */
512
513                                 ProcessTimeRanges(kv.second, &reference, segments);
514                         }
515                 }
516         }
517
518         Log(LogDebug, "LegacyTimePeriod")
519                 << "Legacy timeperiod update returned " << segments->GetLength() << " segments.";
520
521         return segments;
522 }