]> granicus.if.org Git - icinga2/blob - lib/icinga/legacytimeperiod.cpp
Merge pull request #6708 from Icinga/bugfix/docs-alpine-repos
[icinga2] / lib / icinga / legacytimeperiod.cpp
1 /******************************************************************************
2  * Icinga 2                                                                   *
3  * Copyright (C) 2012-2018 Icinga Development Team (https://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::FindRunningSegment(const String& daydef, const String& timeranges, tm *reference)
392 {
393         tm begin, end, iter;
394         time_t tsend, tsiter, tsref;
395         int stride;
396
397         tsref = mktime(reference);
398
399         ParseTimeRange(daydef, &begin, &end, &stride, reference);
400
401         iter = begin;
402
403         tsend = mktime(&end);
404
405         do {
406                 if (IsInTimeRange(&begin, &end, stride, &iter)) {
407                         Array::Ptr segments = new Array();
408                         ProcessTimeRanges(timeranges, &iter, segments);
409
410                         Dictionary::Ptr bestSegment;
411                         double bestEnd;
412
413                         ObjectLock olock(segments);
414                         for (const Dictionary::Ptr& segment : segments) {
415                                 double begin = segment->Get("begin");
416                                 double end = segment->Get("end");
417
418                                 if (begin >= tsref || end < tsref)
419                                         continue;
420
421                                 if (!bestSegment || end > bestEnd) {
422                                         bestSegment = segment;
423                                         bestEnd = end;
424                                 }
425                         }
426
427                         if (bestSegment)
428                                 return bestSegment;
429                 }
430
431                 iter.tm_mday++;
432                 iter.tm_hour = 0;
433                 iter.tm_min = 0;
434                 iter.tm_sec = 0;
435                 tsiter = mktime(&iter);
436         } while (tsiter < tsend);
437
438         return nullptr;
439 }
440
441 Dictionary::Ptr LegacyTimePeriod::FindNextSegment(const String& daydef, const String& timeranges, tm *reference)
442 {
443         tm begin, end, iter, ref;
444         time_t tsend, tsiter, tsref;
445         int stride;
446
447         for (int pass = 1; pass <= 2; pass++) {
448                 if (pass == 1) {
449                         ref = *reference;
450                 } else {
451                         ref = end;
452                         ref.tm_mday++;
453                 }
454
455                 tsref = mktime(&ref);
456
457                 ParseTimeRange(daydef, &begin, &end, &stride, &ref);
458
459                 iter = begin;
460
461                 tsend = mktime(&end);
462
463                 do {
464                         if (IsInTimeRange(&begin, &end, stride, &iter)) {
465                                 Array::Ptr segments = new Array();
466                                 ProcessTimeRanges(timeranges, &iter, segments);
467
468                                 Dictionary::Ptr bestSegment;
469                                 double bestBegin;
470
471                                 ObjectLock olock(segments);
472                                 for (const Dictionary::Ptr& segment : segments) {
473                                         double begin = segment->Get("begin");
474
475                                         if (begin < tsref)
476                                                 continue;
477
478                                         if (!bestSegment || begin < bestBegin) {
479                                                 bestSegment = segment;
480                                                 bestBegin = begin;
481                                         }
482                                 }
483
484                                 if (bestSegment)
485                                         return bestSegment;
486                         }
487
488                         iter.tm_mday++;
489                         iter.tm_hour = 0;
490                         iter.tm_min = 0;
491                         iter.tm_sec = 0;
492                         tsiter = mktime(&iter);
493                 } while (tsiter < tsend);
494         }
495
496         return nullptr;
497 }
498
499 Array::Ptr LegacyTimePeriod::ScriptFunc(const TimePeriod::Ptr& tp, double begin, double end)
500 {
501         Array::Ptr segments = new Array();
502
503         Dictionary::Ptr ranges = tp->GetRanges();
504
505         if (ranges) {
506                 for (int i = 0; i <= (end - begin) / (24 * 60 * 60); i++) {
507                         time_t refts = begin + i * 24 * 60 * 60;
508                         tm reference = Utility::LocalTime(refts);
509
510 #ifdef I2_DEBUG
511                         Log(LogDebug, "LegacyTimePeriod")
512                                 << "Checking reference time " << refts;
513 #endif /* I2_DEBUG */
514
515                         ObjectLock olock(ranges);
516                         for (const Dictionary::Pair& kv : ranges) {
517                                 if (!IsInDayDefinition(kv.first, &reference)) {
518 #ifdef I2_DEBUG
519                                         Log(LogDebug, "LegacyTimePeriod")
520                                                 << "Not in day definition '" << kv.first << "'.";
521 #endif /* I2_DEBUG */
522                                         continue;
523                                 }
524
525 #ifdef I2_DEBUG
526                                 Log(LogDebug, "LegacyTimePeriod")
527                                         << "In day definition '" << kv.first << "'.";
528 #endif /* I2_DEBUG */
529
530                                 ProcessTimeRanges(kv.second, &reference, segments);
531                         }
532                 }
533         }
534
535         Log(LogDebug, "LegacyTimePeriod")
536                 << "Legacy timeperiod update returned " << segments->GetLength() << " segments.";
537
538         return segments;
539 }