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