Fix: liblttng-ctl: unreported truncations when copying strings
[lttng-tools.git] / src / lib / lttng-ctl / rotate.c
CommitLineData
d68c9a04
JD
1/*
2 * Copyright (C) 2017 - Julien Desfossez <jdesfossez@efficios.com>
3 *
4 * This library is free software; you can redistribute it and/or modify it
5 * under the terms of the GNU Lesser General Public License, version 2.1 only,
6 * as published by the Free Software Foundation.
7 *
8 * This library is distributed in the hope that it will be useful, but WITHOUT
9 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
10 * FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License
11 * for more details.
12 *
13 * You should have received a copy of the GNU Lesser General Public License
14 * along with this library; if not, write to the Free Software Foundation,
15 * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
16 */
17
18#define _LGPL_SOURCE
19#include <assert.h>
20#include <string.h>
21
22#include <lttng/lttng-error.h>
23#include <lttng/rotation.h>
dd73d57b 24#include <lttng/location-internal.h>
d68c9a04
JD
25#include <lttng/rotate-internal.h>
26#include <common/sessiond-comm/sessiond-comm.h>
27#include <common/macros.h>
28
29#include "lttng-ctl-helper.h"
30
d68c9a04
JD
31static
32enum lttng_rotation_status ask_rotation_info(
33 struct lttng_rotation_handle *rotation_handle,
34 struct lttng_rotation_get_info_return **info)
35{
36 /* lsm.get_rotation_state.rotation_id */
37 struct lttcomm_session_msg lsm;
38 enum lttng_rotation_status status = LTTNG_ROTATION_STATUS_OK;
39 int ret;
40
41 if (!rotation_handle || !info) {
42 status = LTTNG_ROTATION_STATUS_INVALID;
43 goto end;
44 }
45
46 memset(&lsm, 0, sizeof(lsm));
47 lsm.cmd_type = LTTNG_ROTATION_GET_INFO;
48 lsm.u.get_rotation_info.rotation_id = rotation_handle->rotation_id;
49
50 ret = lttng_strncpy(lsm.session.name, rotation_handle->session_name,
51 sizeof(lsm.session.name));
52 if (ret) {
53 status = LTTNG_ROTATION_STATUS_INVALID;
54 goto end;
55 }
56
57 ret = lttng_ctl_ask_sessiond(&lsm, (void **) info);
58 if (ret < 0) {
59 status = LTTNG_ROTATION_STATUS_ERROR;
60 goto end;
61 }
62end:
63 return status;
64
65}
66
dd73d57b
JG
67static
68struct lttng_trace_archive_location *
69create_trace_archive_location_from_get_info(
70 const struct lttng_rotation_get_info_return *info)
71{
72 struct lttng_trace_archive_location *location;
73
74 switch (info->location_type) {
75 case LTTNG_TRACE_ARCHIVE_LOCATION_TYPE_LOCAL:
76 location = lttng_trace_archive_location_local_create(
77 info->location.local.absolute_path);
78 break;
79 case LTTNG_TRACE_ARCHIVE_LOCATION_TYPE_RELAY:
80 location = lttng_trace_archive_location_relay_create(
81 info->location.relay.host,
82 info->location.relay.protocol,
83 info->location.relay.ports.control,
84 info->location.relay.ports.data,
85 info->location.relay.relative_path);
86 break;
87 default:
88 location = NULL;
89 break;
90 }
91 return location;
92}
93
d68c9a04
JD
94enum lttng_rotation_status lttng_rotation_handle_get_state(
95 struct lttng_rotation_handle *rotation_handle,
96 enum lttng_rotation_state *state)
97{
98 enum lttng_rotation_status status = LTTNG_ROTATION_STATUS_OK;
99 struct lttng_rotation_get_info_return *info = NULL;
d68c9a04
JD
100
101 if (!rotation_handle || !state) {
102 status = LTTNG_ROTATION_STATUS_INVALID;
103 goto end;
104 }
105
106 status = ask_rotation_info(rotation_handle, &info);
107 if (status != LTTNG_ROTATION_STATUS_OK) {
108 goto end;
109 }
110
111 *state = (enum lttng_rotation_state) info->status;
dd73d57b 112 if (rotation_handle->archive_location ||
d68c9a04
JD
113 *state != LTTNG_ROTATION_STATE_COMPLETED) {
114 /*
115 * The path is only provided by the sessiond once
116 * the session rotation is completed, but not expired.
117 */
118 goto end;
119 }
120
121 /*
122 * Cache the location since the rotation may expire before the user
123 * has a chance to query it.
124 */
dd73d57b
JG
125 rotation_handle->archive_location =
126 create_trace_archive_location_from_get_info(info);
127 if (!rotation_handle->archive_location) {
d68c9a04
JD
128 status = LTTNG_ROTATION_STATUS_ERROR;
129 goto end;
130 }
d68c9a04
JD
131end:
132 free(info);
133 return status;
134}
135
dd73d57b 136enum lttng_rotation_status lttng_rotation_handle_get_archive_location(
d68c9a04 137 struct lttng_rotation_handle *rotation_handle,
dd73d57b 138 const struct lttng_trace_archive_location **location)
d68c9a04 139{
d68c9a04
JD
140 enum lttng_rotation_status status = LTTNG_ROTATION_STATUS_OK;
141 struct lttng_rotation_get_info_return *info = NULL;
142
dd73d57b 143 if (!rotation_handle || !location) {
d68c9a04
JD
144 status = LTTNG_ROTATION_STATUS_INVALID;
145 goto end;
146 }
147
148 /* Use the cached location we got from a previous query. */
dd73d57b
JG
149 if (rotation_handle->archive_location) {
150 *location = rotation_handle->archive_location;
d68c9a04
JD
151 goto end;
152 }
153
154 status = ask_rotation_info(rotation_handle, &info);
155 if (status != LTTNG_ROTATION_STATUS_OK) {
156 goto end;
157 }
158
159 if ((enum lttng_rotation_state) info->status !=
160 LTTNG_ROTATION_STATE_COMPLETED) {
161 status = LTTNG_ROTATION_STATUS_UNAVAILABLE;
162 goto end;
163 }
164
dd73d57b
JG
165 rotation_handle->archive_location =
166 create_trace_archive_location_from_get_info(info);
167 if (!rotation_handle->archive_location) {
d68c9a04
JD
168 status = LTTNG_ROTATION_STATUS_ERROR;
169 goto end;
170 }
d68c9a04
JD
171end:
172 free(info);
173 return status;
174}
175
176void lttng_rotation_handle_destroy(
177 struct lttng_rotation_handle *rotation_handle)
178{
06b180a1
JR
179 if (!rotation_handle) {
180 return;
181 }
dd73d57b 182 lttng_trace_archive_location_destroy(rotation_handle->archive_location);
d68c9a04
JD
183 free(rotation_handle);
184}
185
186static
187int init_rotation_handle(struct lttng_rotation_handle *rotation_handle,
dbd512ea 188 const char *session_name,
66ea93b1 189 struct lttng_rotate_session_return *rotate_return)
d68c9a04
JD
190{
191 int ret;
192
dbd512ea 193 ret = lttng_strncpy(rotation_handle->session_name, session_name,
d68c9a04
JD
194 sizeof(rotation_handle->session_name));
195 if (ret) {
196 goto end;
197 }
198
199 rotation_handle->rotation_id = rotate_return->rotation_id;
200end:
201 return ret;
202}
203
204/*
205 * Rotate the output folder of the session.
206 *
207 * Return 0 on success else a negative LTTng error code.
208 */
dbd512ea 209int lttng_rotate_session(const char *session_name,
66ea93b1 210 struct lttng_rotation_immediate_descriptor *descriptor,
d68c9a04
JD
211 struct lttng_rotation_handle **rotation_handle)
212{
213 struct lttcomm_session_msg lsm;
214 struct lttng_rotate_session_return *rotate_return = NULL;
215 int ret;
dbd512ea 216 size_t session_name_len;
d68c9a04 217
dbd512ea
JG
218 if (!session_name) {
219 ret = -LTTNG_ERR_INVALID;
220 goto end;
221 }
222
223 session_name_len = strlen(session_name);
224 if (session_name_len >= sizeof(lsm.session.name) ||
225 session_name_len >= member_sizeof(struct lttng_rotation_handle, session_name)) {
d68c9a04
JD
226 ret = -LTTNG_ERR_INVALID;
227 goto end;
228 }
229
230 memset(&lsm, 0, sizeof(lsm));
231 lsm.cmd_type = LTTNG_ROTATE_SESSION;
55fb8091
JG
232
233 ret = lttng_strncpy(lsm.session.name, session_name,
234 sizeof(lsm.session.name));
235 /* Source length already validated. */
236 assert(ret == 0);
d68c9a04
JD
237
238 ret = lttng_ctl_ask_sessiond(&lsm, (void **) &rotate_return);
6e8ac53d 239 if (ret <= 0) {
d68c9a04
JD
240 *rotation_handle = NULL;
241 goto end;
242 }
243
244 *rotation_handle = zmalloc(sizeof(struct lttng_rotation_handle));
245 if (!*rotation_handle) {
246 ret = -LTTNG_ERR_NOMEM;
247 goto end;
248 }
249
66ea93b1 250 init_rotation_handle(*rotation_handle, session_name, rotate_return);
d68c9a04
JD
251
252 ret = 0;
253
254end:
255 free(rotate_return);
256 return ret;
257}
259c2674
JD
258
259/*
66ea93b1
JG
260 * Update the automatic rotation parameters.
261 * 'add' as true enables the provided schedule, false removes the shedule.
262 *
263 * The external API makes it appear as though arbitrary schedules can
264 * be added or removed at will. However, the session daemon is
265 * currently limited to one schedule per type (per session).
266 *
267 * The additional flexibility of the public API is offered for future
268 * rotation schedules that could indicate more precise criteria than
269 * size and time (e.g. a domain) where it could make sense to add
270 * multiple schedules of a given type to a session.
271 *
272 * Hence, the exact schedule that the user wishes to remove (and not
273 * just its type) must be passed so that the session daemon can
274 * validate that is exists before clearing it.
259c2674 275 */
66ea93b1
JG
276static
277enum lttng_rotation_status lttng_rotation_update_schedule(
278 const char *session_name,
279 const struct lttng_rotation_schedule *schedule,
280 bool add)
259c2674
JD
281{
282 struct lttcomm_session_msg lsm;
66ea93b1 283 enum lttng_rotation_status status = LTTNG_ROTATION_STATUS_OK;
259c2674
JD
284 int ret;
285
66ea93b1
JG
286 if (!session_name || !schedule) {
287 status = LTTNG_ROTATION_STATUS_INVALID;
dbd512ea
JG
288 goto end;
289 }
290
291 if (strlen(session_name) >= sizeof(lsm.session.name)) {
66ea93b1 292 status = LTTNG_ROTATION_STATUS_INVALID;
259c2674
JD
293 goto end;
294 }
295
296 memset(&lsm, 0, sizeof(lsm));
297 lsm.cmd_type = LTTNG_ROTATION_SET_SCHEDULE;
55fb8091 298 ret = lttng_strncpy(lsm.session.name, session_name,
259c2674 299 sizeof(lsm.session.name));
55fb8091
JG
300 /* Source length already validated. */
301 assert(ret == 0);
66ea93b1
JG
302
303 lsm.u.rotation_set_schedule.type = (uint32_t) schedule->type;
304 switch (schedule->type) {
305 case LTTNG_ROTATION_SCHEDULE_TYPE_SIZE_THRESHOLD:
306 {
18cf1ffb
JG
307 uint64_t threshold;
308
66ea93b1 309 status = lttng_rotation_schedule_size_threshold_get_threshold(
18cf1ffb 310 schedule, &threshold);
66ea93b1 311 if (status != LTTNG_ROTATION_STATUS_OK) {
ed9f1fb2
JG
312 if (status == LTTNG_ROTATION_STATUS_UNAVAILABLE) {
313 status = LTTNG_ROTATION_STATUS_INVALID;
314 }
66ea93b1
JG
315 goto end;
316 }
18cf1ffb 317 lsm.u.rotation_set_schedule.value = threshold;
66ea93b1
JG
318 lsm.u.rotation_set_schedule.set = !!add;
319 break;
320 }
321 case LTTNG_ROTATION_SCHEDULE_TYPE_PERIODIC:
322 {
18cf1ffb
JG
323 uint64_t period;
324
66ea93b1 325 status = lttng_rotation_schedule_periodic_get_period(
18cf1ffb 326 schedule, &period);
66ea93b1 327 if (status != LTTNG_ROTATION_STATUS_OK) {
ed9f1fb2
JG
328 if (status == LTTNG_ROTATION_STATUS_UNAVAILABLE) {
329 status = LTTNG_ROTATION_STATUS_INVALID;
330 }
66ea93b1
JG
331 goto end;
332 }
18cf1ffb 333 lsm.u.rotation_set_schedule.value = period;
66ea93b1
JG
334 lsm.u.rotation_set_schedule.set = !!add;
335 break;
336 }
337 default:
338 status = LTTNG_ROTATION_STATUS_INVALID;
339 goto end;
340 }
259c2674
JD
341
342 ret = lttng_ctl_ask_sessiond(&lsm, NULL);
66ea93b1
JG
343 if (ret >= 0) {
344 goto end;
345 }
346
347 switch (-ret) {
348 case LTTNG_ERR_ROTATION_SCHEDULE_SET:
349 status = LTTNG_ROTATION_STATUS_SCHEDULE_ALREADY_SET;
350 break;
351 case LTTNG_ERR_ROTATION_SCHEDULE_NOT_SET:
352 status = LTTNG_ROTATION_STATUS_INVALID;
353 break;
354 default:
355 status = LTTNG_ROTATION_STATUS_ERROR;
356 }
259c2674 357end:
66ea93b1
JG
358 return status;
359}
360
361static
362struct lttng_rotation_schedules *lttng_rotation_schedules_create(void)
363{
364 return zmalloc(sizeof(struct lttng_rotation_schedules));
259c2674 365}
329f3443 366
66ea93b1
JG
367static
368void lttng_schedules_add(struct lttng_rotation_schedules *schedules,
369 struct lttng_rotation_schedule *schedule)
370{
371 schedules->schedules[schedules->count++] = schedule;
372}
373
374static
375int get_schedules(const char *session_name,
376 struct lttng_rotation_schedules **_schedules)
329f3443 377{
329f3443 378 int ret;
66ea93b1 379 struct lttcomm_session_msg lsm;
adc8eccb 380 struct lttng_session_list_schedules_return *schedules_comm = NULL;
66ea93b1
JG
381 struct lttng_rotation_schedules *schedules = NULL;
382 struct lttng_rotation_schedule *periodic = NULL, *size = NULL;
329f3443 383
55fb8091
JG
384 if (!session_name) {
385 ret = -LTTNG_ERR_INVALID;
386 goto end;
387 }
388
329f3443 389 memset(&lsm, 0, sizeof(lsm));
66ea93b1 390 lsm.cmd_type = LTTNG_SESSION_LIST_ROTATION_SCHEDULES;
55fb8091 391 ret = lttng_strncpy(lsm.session.name, session_name,
329f3443 392 sizeof(lsm.session.name));
55fb8091
JG
393 if (ret) {
394 ret = -LTTNG_ERR_INVALID;
395 goto end;
396 }
329f3443 397
66ea93b1 398 ret = lttng_ctl_ask_sessiond(&lsm, (void **) &schedules_comm);
329f3443 399 if (ret < 0) {
329f3443
JD
400 goto end;
401 }
402
66ea93b1
JG
403 schedules = lttng_rotation_schedules_create();
404 if (!schedules) {
405 ret = -LTTNG_ERR_NOMEM;
406 goto end;
407 }
408
409 if (schedules_comm->periodic.set == 1) {
410 enum lttng_rotation_status status;
411
412 periodic = lttng_rotation_schedule_periodic_create();
413 if (!periodic) {
414 ret = -LTTNG_ERR_NOMEM;
415 goto end;
416 }
417
418 status = lttng_rotation_schedule_periodic_set_period(
419 periodic, schedules_comm->periodic.value);
420 if (status != LTTNG_ROTATION_STATUS_OK) {
421 /*
422 * This would imply that the session daemon returned
423 * an invalid periodic rotation schedule value.
424 */
425 ret = -LTTNG_ERR_UNK;
426 goto end;
427 }
428
429 lttng_schedules_add(schedules, periodic);
430 periodic = NULL;
431 }
432
433 if (schedules_comm->size.set == 1) {
434 enum lttng_rotation_status status;
435
436 size = lttng_rotation_schedule_size_threshold_create();
437 if (!size) {
438 ret = -LTTNG_ERR_NOMEM;
439 goto end;
440 }
441
442 status = lttng_rotation_schedule_size_threshold_set_threshold(
443 size, schedules_comm->size.value);
444 if (status != LTTNG_ROTATION_STATUS_OK) {
445 /*
446 * This would imply that the session daemon returned
447 * an invalid size threshold schedule value.
448 */
449 ret = -LTTNG_ERR_UNK;
450 goto end;
451 }
452
453 lttng_schedules_add(schedules, size);
454 size = NULL;
455 }
456
457 ret = LTTNG_OK;
329f3443 458end:
66ea93b1
JG
459 free(schedules_comm);
460 free(periodic);
461 free(size);
462 *_schedules = schedules;
329f3443
JD
463 return ret;
464}
465
66ea93b1
JG
466enum lttng_rotation_schedule_type lttng_rotation_schedule_get_type(
467 const struct lttng_rotation_schedule *schedule)
329f3443 468{
66ea93b1
JG
469 return schedule ? schedule->type : LTTNG_ROTATION_SCHEDULE_TYPE_UNKNOWN;
470}
329f3443 471
66ea93b1
JG
472struct lttng_rotation_schedule *
473lttng_rotation_schedule_size_threshold_create(void)
474{
475 struct lttng_rotation_schedule_size_threshold *schedule;
329f3443 476
66ea93b1
JG
477 schedule = zmalloc(sizeof(*schedule));
478 if (!schedule) {
329f3443
JD
479 goto end;
480 }
481
66ea93b1
JG
482 schedule->parent.type = LTTNG_ROTATION_SCHEDULE_TYPE_SIZE_THRESHOLD;
483end:
484 return &schedule->parent;
485}
486
487enum lttng_rotation_status
488lttng_rotation_schedule_size_threshold_get_threshold(
489 const struct lttng_rotation_schedule *schedule,
490 uint64_t *size_threshold_bytes)
491{
492 enum lttng_rotation_status status = LTTNG_ROTATION_STATUS_OK;
493 struct lttng_rotation_schedule_size_threshold *size_schedule;
329f3443 494
ed9f1fb2
JG
495 if (!schedule || !size_threshold_bytes ||
496 schedule->type != LTTNG_ROTATION_SCHEDULE_TYPE_SIZE_THRESHOLD) {
66ea93b1
JG
497 status = LTTNG_ROTATION_STATUS_INVALID;
498 goto end;
499 }
500
501 size_schedule = container_of(schedule,
502 struct lttng_rotation_schedule_size_threshold,
503 parent);
504 if (size_schedule->size.set) {
505 *size_threshold_bytes = size_schedule->size.bytes;
506 } else {
507 status = LTTNG_ROTATION_STATUS_UNAVAILABLE;
508 goto end;
509 }
510end:
511 return status;
512}
513
514enum lttng_rotation_status
515lttng_rotation_schedule_size_threshold_set_threshold(
516 struct lttng_rotation_schedule *schedule,
517 uint64_t size_threshold_bytes)
518{
519 enum lttng_rotation_status status = LTTNG_ROTATION_STATUS_OK;
520 struct lttng_rotation_schedule_size_threshold *size_schedule;
521
522 if (!schedule || size_threshold_bytes == 0 ||
ed9f1fb2
JG
523 size_threshold_bytes == -1ULL ||
524 schedule->type != LTTNG_ROTATION_SCHEDULE_TYPE_SIZE_THRESHOLD) {
66ea93b1
JG
525 status = LTTNG_ROTATION_STATUS_INVALID;
526 goto end;
527 }
329f3443 528
66ea93b1
JG
529 size_schedule = container_of(schedule,
530 struct lttng_rotation_schedule_size_threshold,
531 parent);
532 size_schedule->size.bytes = size_threshold_bytes;
533 size_schedule->size.set = true;
329f3443 534end:
66ea93b1
JG
535 return status;
536}
537
538struct lttng_rotation_schedule *
539lttng_rotation_schedule_periodic_create(void)
540{
541 struct lttng_rotation_schedule_periodic *schedule;
542
543 schedule = zmalloc(sizeof(*schedule));
544 if (!schedule) {
545 goto end;
546 }
547
548 schedule->parent.type = LTTNG_ROTATION_SCHEDULE_TYPE_PERIODIC;
549end:
550 return &schedule->parent;
551}
552
553enum lttng_rotation_status
554lttng_rotation_schedule_periodic_get_period(
555 const struct lttng_rotation_schedule *schedule,
556 uint64_t *period_us)
557{
558 enum lttng_rotation_status status = LTTNG_ROTATION_STATUS_OK;
559 struct lttng_rotation_schedule_periodic *periodic_schedule;
560
ed9f1fb2
JG
561 if (!schedule || !period_us ||
562 schedule->type != LTTNG_ROTATION_SCHEDULE_TYPE_PERIODIC) {
66ea93b1
JG
563 status = LTTNG_ROTATION_STATUS_INVALID;
564 goto end;
565 }
566
567 periodic_schedule = container_of(schedule,
568 struct lttng_rotation_schedule_periodic,
569 parent);
570 if (periodic_schedule->period.set) {
571 *period_us = periodic_schedule->period.us;
572 } else {
573 status = LTTNG_ROTATION_STATUS_UNAVAILABLE;
574 goto end;
575 }
576end:
577 return status;
578}
579
580enum lttng_rotation_status
581lttng_rotation_schedule_periodic_set_period(
582 struct lttng_rotation_schedule *schedule,
583 uint64_t period_us)
584{
585 enum lttng_rotation_status status = LTTNG_ROTATION_STATUS_OK;
586 struct lttng_rotation_schedule_periodic *periodic_schedule;
587
ed9f1fb2
JG
588 if (!schedule || period_us == 0 || period_us == -1ULL ||
589 schedule->type != LTTNG_ROTATION_SCHEDULE_TYPE_PERIODIC) {
66ea93b1
JG
590 status = LTTNG_ROTATION_STATUS_INVALID;
591 goto end;
592 }
593
594 periodic_schedule = container_of(schedule,
595 struct lttng_rotation_schedule_periodic,
596 parent);
597 periodic_schedule->period.us = period_us;
598 periodic_schedule->period.set = true;
599end:
600 return status;
601}
602
603void lttng_rotation_schedule_destroy(struct lttng_rotation_schedule *schedule)
604{
605 if (!schedule) {
606 return;
607 }
608 free(schedule);
609}
610
611void lttng_rotation_schedules_destroy(
612 struct lttng_rotation_schedules *schedules)
613{
614 unsigned int i;
615
616 if (!schedules) {
617 return;
618 }
619
620 for (i = 0; i < schedules->count; i++) {
621 lttng_rotation_schedule_destroy(schedules->schedules[i]);
622 }
623 free(schedules);
624}
625
626
627enum lttng_rotation_status lttng_rotation_schedules_get_count(
628 const struct lttng_rotation_schedules *schedules,
629 unsigned int *count)
630{
631 enum lttng_rotation_status status = LTTNG_ROTATION_STATUS_OK;
632
633 if (!schedules || !count) {
634 status = LTTNG_ROTATION_STATUS_INVALID;
635 goto end;
636 }
637
638 *count = schedules->count;
639end:
640 return status;
641}
642
643const struct lttng_rotation_schedule *lttng_rotation_schedules_get_at_index(
644 const struct lttng_rotation_schedules *schedules,
645 unsigned int index)
646{
647 const struct lttng_rotation_schedule *schedule = NULL;
648
649 if (!schedules || index >= schedules->count) {
650 goto end;
651 }
652
653 schedule = schedules->schedules[index];
654end:
655 return schedule;
656}
657
658enum lttng_rotation_status lttng_session_add_rotation_schedule(
659 const char *session_name,
660 const struct lttng_rotation_schedule *schedule)
661{
662 return lttng_rotation_update_schedule(session_name, schedule, true);
663}
664
665enum lttng_rotation_status lttng_session_remove_rotation_schedule(
666 const char *session_name,
667 const struct lttng_rotation_schedule *schedule)
668{
669 return lttng_rotation_update_schedule(session_name, schedule, false);
670}
671
672int lttng_session_list_rotation_schedules(
673 const char *session_name,
674 struct lttng_rotation_schedules **schedules)
675{
676 return get_schedules(session_name, schedules);
329f3443 677}
This page took 0.101728 seconds and 4 git commands to generate.