Line data Source code
1 : /*
2 : Authors:
3 : Pavel Březina <pbrezina@redhat.com>
4 :
5 : Copyright (C) 2013 Red Hat
6 :
7 : This program is free software; you can redistribute it and/or modify
8 : it under the terms of the GNU General Public License as published by
9 : the Free Software Foundation; either version 3 of the License, or
10 : (at your option) any later version.
11 :
12 : This program is distributed in the hope that it will be useful,
13 : but WITHOUT ANY WARRANTY; without even the implied warranty of
14 : MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15 : GNU General Public License for more details.
16 :
17 : You should have received a copy of the GNU General Public License
18 : along with this program. If not, see <http://www.gnu.org/licenses/>.
19 : */
20 :
21 : #include <tevent.h>
22 : #include <talloc.h>
23 : #include <time.h>
24 : #include <string.h>
25 :
26 : #include "util/util.h"
27 : #include "providers/backend.h"
28 : #include "providers/be_ptask_private.h"
29 : #include "providers/be_ptask.h"
30 :
31 : #define backoff_allowed(ptask) (ptask->max_backoff != 0)
32 :
33 : enum be_ptask_schedule {
34 : BE_PTASK_SCHEDULE_FROM_NOW,
35 : BE_PTASK_SCHEDULE_FROM_LAST
36 : };
37 :
38 : enum be_ptask_delay {
39 : BE_PTASK_FIRST_DELAY,
40 : BE_PTASK_ENABLED_DELAY,
41 : BE_PTASK_PERIOD
42 : };
43 :
44 : static void be_ptask_schedule(struct be_ptask *task,
45 : enum be_ptask_delay delay_type,
46 : enum be_ptask_schedule from);
47 :
48 19 : static int be_ptask_destructor(void *pvt)
49 : {
50 : struct be_ptask *task;
51 :
52 19 : task = talloc_get_type(pvt, struct be_ptask);
53 19 : if (task == NULL) {
54 0 : DEBUG(SSSDBG_FATAL_FAILURE, "BUG: task is NULL\n");
55 0 : return 0;
56 : }
57 :
58 19 : DEBUG(SSSDBG_TRACE_FUNC, "Terminating periodic task [%s]\n", task->name);
59 :
60 19 : return 0;
61 : }
62 :
63 0 : static void be_ptask_online_cb(void *pvt)
64 : {
65 0 : struct be_ptask *task = NULL;
66 :
67 0 : task = talloc_get_type(pvt, struct be_ptask);
68 0 : if (task == NULL) {
69 0 : DEBUG(SSSDBG_FATAL_FAILURE, "BUG: task is NULL\n");
70 0 : return;
71 : }
72 :
73 0 : DEBUG(SSSDBG_TRACE_FUNC, "Back end is online\n");
74 0 : be_ptask_enable(task);
75 : }
76 :
77 0 : static void be_ptask_offline_cb(void *pvt)
78 : {
79 0 : struct be_ptask *task = NULL;
80 0 : task = talloc_get_type(pvt, struct be_ptask);
81 :
82 0 : DEBUG(SSSDBG_TRACE_FUNC, "Back end is offline\n");
83 0 : be_ptask_disable(task);
84 0 : }
85 :
86 1 : static void be_ptask_timeout(struct tevent_context *ev,
87 : struct tevent_timer *tt,
88 : struct timeval tv,
89 : void *pvt)
90 : {
91 1 : struct be_ptask *task = NULL;
92 1 : task = talloc_get_type(pvt, struct be_ptask);
93 :
94 1 : DEBUG(SSSDBG_OP_FAILURE, "Task [%s]: timed out\n", task->name);
95 :
96 1 : talloc_zfree(task->req);
97 1 : be_ptask_schedule(task, BE_PTASK_PERIOD, BE_PTASK_SCHEDULE_FROM_NOW);
98 1 : }
99 :
100 : static void be_ptask_done(struct tevent_req *req);
101 :
102 19 : static void be_ptask_execute(struct tevent_context *ev,
103 : struct tevent_timer *tt,
104 : struct timeval tv,
105 : void *pvt)
106 : {
107 19 : struct be_ptask *task = NULL;
108 19 : struct tevent_timer *timeout = NULL;
109 :
110 19 : task = talloc_get_type(pvt, struct be_ptask);
111 19 : task->timer = NULL; /* timer is freed by tevent */
112 :
113 19 : if (be_is_offline(task->be_ctx)) {
114 3 : DEBUG(SSSDBG_TRACE_FUNC, "Back end is offline\n");
115 3 : switch (task->offline) {
116 : case BE_PTASK_OFFLINE_SKIP:
117 1 : be_ptask_schedule(task, BE_PTASK_PERIOD,
118 : BE_PTASK_SCHEDULE_FROM_NOW);
119 1 : return;
120 : case BE_PTASK_OFFLINE_DISABLE:
121 : /* This case is normally handled by offline callback but we
122 : * should handle it here as well since we can get here in some
123 : * special cases for example unit tests or tevent events order. */
124 1 : be_ptask_disable(task);
125 1 : return;
126 : case BE_PTASK_OFFLINE_EXECUTE:
127 : /* continue */
128 1 : break;
129 : }
130 : }
131 :
132 17 : DEBUG(SSSDBG_TRACE_FUNC, "Task [%s]: executing task, timeout %lu "
133 : "seconds\n", task->name, task->timeout);
134 :
135 17 : task->last_execution = tv.tv_sec;
136 :
137 17 : task->req = task->send_fn(task, task->ev, task->be_ctx, task, task->pvt);
138 17 : if (task->req == NULL) {
139 : /* skip this iteration and try again later */
140 1 : DEBUG(SSSDBG_OP_FAILURE, "Task [%s]: failed to execute task, "
141 : "will try again later\n", task->name);
142 :
143 1 : be_ptask_schedule(task, BE_PTASK_PERIOD, BE_PTASK_SCHEDULE_FROM_NOW);
144 1 : return;
145 : }
146 :
147 16 : tevent_req_set_callback(task->req, be_ptask_done, task);
148 :
149 : /* schedule timeout */
150 16 : if (task->timeout > 0) {
151 1 : tv = tevent_timeval_current_ofs(task->timeout, 0);
152 1 : timeout = tevent_add_timer(task->ev, task->req, tv,
153 : be_ptask_timeout, task);
154 1 : if (timeout == NULL) {
155 : /* If we can't guarantee a timeout,
156 : * we need to cancel the request. */
157 0 : talloc_zfree(task->req);
158 :
159 0 : DEBUG(SSSDBG_OP_FAILURE, "Task [%s]: failed to set timeout, "
160 : "the task will be rescheduled\n", task->name);
161 :
162 0 : be_ptask_schedule(task, BE_PTASK_PERIOD,
163 : BE_PTASK_SCHEDULE_FROM_NOW);
164 : }
165 : }
166 :
167 16 : return;
168 : }
169 :
170 14 : static void be_ptask_done(struct tevent_req *req)
171 : {
172 14 : struct be_ptask *task = NULL;
173 : errno_t ret;
174 :
175 14 : task = tevent_req_callback_data(req, struct be_ptask);
176 :
177 14 : ret = task->recv_fn(req);
178 14 : talloc_zfree(req);
179 14 : task->req = NULL;
180 14 : switch (ret) {
181 : case EOK:
182 10 : DEBUG(SSSDBG_TRACE_FUNC, "Task [%s]: finished successfully\n",
183 : task->name);
184 :
185 10 : be_ptask_schedule(task, BE_PTASK_PERIOD, BE_PTASK_SCHEDULE_FROM_LAST);
186 10 : break;
187 : default:
188 4 : DEBUG(SSSDBG_OP_FAILURE, "Task [%s]: failed with [%d]: %s\n",
189 : task->name, ret, sss_strerror(ret));
190 :
191 4 : be_ptask_schedule(task, BE_PTASK_PERIOD, BE_PTASK_SCHEDULE_FROM_NOW);
192 4 : break;
193 : }
194 14 : }
195 :
196 38 : static void be_ptask_schedule(struct be_ptask *task,
197 : enum be_ptask_delay delay_type,
198 : enum be_ptask_schedule from)
199 : {
200 38 : struct timeval tv = { 0, };
201 38 : time_t delay = 0;
202 :
203 38 : if (!task->enabled) {
204 0 : DEBUG(SSSDBG_TRACE_FUNC, "Task [%s]: disabled\n", task->name);
205 38 : return;
206 : }
207 :
208 38 : switch (delay_type) {
209 : case BE_PTASK_FIRST_DELAY:
210 19 : delay = task->first_delay;
211 19 : break;
212 : case BE_PTASK_ENABLED_DELAY:
213 2 : delay = task->enabled_delay;
214 2 : break;
215 : case BE_PTASK_PERIOD:
216 17 : delay = task->period;
217 :
218 17 : if (backoff_allowed(task) && task->period * 2 <= task->max_backoff) {
219 : /* double the period for the next execution */
220 2 : task->period *= 2;
221 : }
222 17 : break;
223 : }
224 :
225 : /* add random offset */
226 38 : if (task->random_offset != 0) {
227 0 : delay = delay + (rand_r(&task->ro_seed) % task->random_offset);
228 : }
229 :
230 38 : switch (from) {
231 : case BE_PTASK_SCHEDULE_FROM_NOW:
232 28 : tv = tevent_timeval_current_ofs(delay, 0);
233 :
234 28 : DEBUG(SSSDBG_TRACE_FUNC, "Task [%s]: scheduling task %lu seconds "
235 : "from now [%lu]\n", task->name, delay, tv.tv_sec);
236 28 : break;
237 : case BE_PTASK_SCHEDULE_FROM_LAST:
238 10 : tv = tevent_timeval_set(task->last_execution + delay, 0);
239 :
240 10 : DEBUG(SSSDBG_TRACE_FUNC, "Task [%s]: scheduling task %lu seconds "
241 : "from last execution time [%lu]\n",
242 : task->name, delay, tv.tv_sec);
243 10 : break;
244 : }
245 :
246 38 : if (task->timer != NULL) {
247 0 : DEBUG(SSSDBG_MINOR_FAILURE, "Task [%s]: another timer is already "
248 : "active?\n", task->name);
249 0 : talloc_zfree(task->timer);
250 : }
251 :
252 38 : task->timer = tevent_add_timer(task->ev, task, tv, be_ptask_execute, task);
253 38 : if (task->timer == NULL) {
254 : /* nothing we can do about it */
255 0 : DEBUG(SSSDBG_CRIT_FAILURE, "FATAL: Unable to schedule task [%s]\n",
256 : task->name);
257 0 : be_ptask_disable(task);
258 : }
259 :
260 38 : task->next_execution = tv.tv_sec;
261 : }
262 :
263 25 : errno_t be_ptask_create(TALLOC_CTX *mem_ctx,
264 : struct be_ctx *be_ctx,
265 : time_t period,
266 : time_t first_delay,
267 : time_t enabled_delay,
268 : time_t random_offset,
269 : time_t timeout,
270 : enum be_ptask_offline offline,
271 : time_t max_backoff,
272 : be_ptask_send_t send_fn,
273 : be_ptask_recv_t recv_fn,
274 : void *pvt,
275 : const char *name,
276 : struct be_ptask **_task)
277 : {
278 25 : struct be_ptask *task = NULL;
279 : errno_t ret;
280 :
281 25 : if (be_ctx == NULL || period == 0 || send_fn == NULL || recv_fn == NULL
282 20 : || name == NULL) {
283 6 : return EINVAL;
284 : }
285 :
286 19 : task = talloc_zero(mem_ctx, struct be_ptask);
287 19 : if (task == NULL) {
288 0 : ret = ENOMEM;
289 0 : goto done;
290 : }
291 :
292 19 : task->ev = be_ctx->ev;
293 19 : task->be_ctx = be_ctx;
294 19 : task->period = period;
295 19 : task->orig_period = period;
296 19 : task->first_delay = first_delay;
297 19 : task->enabled_delay = enabled_delay;
298 19 : task->random_offset = random_offset;
299 19 : task->ro_seed = time(NULL) * getpid();
300 19 : task->max_backoff = max_backoff;
301 19 : task->timeout = timeout;
302 19 : task->offline = offline;
303 19 : task->send_fn = send_fn;
304 19 : task->recv_fn = recv_fn;
305 19 : task->pvt = pvt;
306 19 : task->name = talloc_strdup(task, name);
307 19 : if (task->name == NULL) {
308 0 : ret = ENOMEM;
309 0 : goto done;
310 : }
311 :
312 19 : task->enabled = true;
313 :
314 19 : talloc_set_destructor((TALLOC_CTX*)task, be_ptask_destructor);
315 :
316 19 : if (offline == BE_PTASK_OFFLINE_DISABLE) {
317 : /* install offline and online callbacks */
318 1 : ret = be_add_online_cb(task, be_ctx, be_ptask_online_cb, task, NULL);
319 1 : if (ret != EOK) {
320 0 : DEBUG(SSSDBG_OP_FAILURE,
321 : "Unable to install online callback [%d]: %s\n",
322 : ret, sss_strerror(ret));
323 0 : goto done;
324 : }
325 :
326 1 : ret = be_add_offline_cb(task, be_ctx, be_ptask_offline_cb, task, NULL);
327 1 : if (ret != EOK) {
328 0 : DEBUG(SSSDBG_OP_FAILURE,
329 : "Unable to install offline callback [%d]: %s\n",
330 : ret, sss_strerror(ret));
331 0 : goto done;
332 : }
333 : }
334 :
335 19 : DEBUG(SSSDBG_TRACE_FUNC, "Periodic task [%s] was created\n", task->name);
336 :
337 19 : be_ptask_schedule(task, BE_PTASK_FIRST_DELAY, BE_PTASK_SCHEDULE_FROM_NOW);
338 :
339 19 : if (_task != NULL) {
340 19 : *_task = task;
341 : }
342 :
343 19 : ret = EOK;
344 :
345 : done:
346 19 : if (ret != EOK) {
347 0 : talloc_free(task);
348 : }
349 :
350 19 : return ret;
351 : }
352 :
353 2 : void be_ptask_enable(struct be_ptask *task)
354 : {
355 2 : if (task->enabled) {
356 0 : DEBUG(SSSDBG_MINOR_FAILURE, "Task [%s]: already enabled\n",
357 : task->name);
358 2 : return;
359 : }
360 :
361 2 : DEBUG(SSSDBG_TRACE_FUNC, "Task [%s]: enabling task\n", task->name);
362 :
363 2 : task->enabled = true;
364 2 : be_ptask_schedule(task, BE_PTASK_ENABLED_DELAY, BE_PTASK_SCHEDULE_FROM_NOW);
365 : }
366 :
367 : /* Disable the task, but if a request already in progress, let it finish. */
368 4 : void be_ptask_disable(struct be_ptask *task)
369 : {
370 4 : DEBUG(SSSDBG_TRACE_FUNC, "Task [%s]: disabling task\n", task->name);
371 :
372 4 : talloc_zfree(task->timer);
373 4 : task->enabled = false;
374 4 : task->period = task->orig_period;
375 4 : }
376 :
377 19 : void be_ptask_destroy(struct be_ptask **task)
378 : {
379 19 : talloc_zfree(*task);
380 19 : }
381 :
382 1 : time_t be_ptask_get_period(struct be_ptask *task)
383 : {
384 1 : return task->period;
385 : }
386 :
387 1 : time_t be_ptask_get_timeout(struct be_ptask *task)
388 : {
389 1 : return task->timeout;
390 : }
391 :
392 : struct be_ptask_sync_ctx {
393 : be_ptask_sync_t fn;
394 : void *pvt;
395 : };
396 :
397 : struct be_ptask_sync_state {
398 : int dummy;
399 : };
400 :
401 : /* This is not an asynchronous request so there is not any _done function. */
402 : static struct tevent_req *
403 5 : be_ptask_sync_send(TALLOC_CTX *mem_ctx,
404 : struct tevent_context *ev,
405 : struct be_ctx *be_ctx,
406 : struct be_ptask *be_ptask,
407 : void *pvt)
408 : {
409 5 : struct be_ptask_sync_ctx *ctx = NULL;
410 5 : struct be_ptask_sync_state *state = NULL;
411 5 : struct tevent_req *req = NULL;
412 : errno_t ret;
413 :
414 5 : req = tevent_req_create(mem_ctx, &state, struct be_ptask_sync_state);
415 5 : if (req == NULL) {
416 0 : DEBUG(SSSDBG_CRIT_FAILURE, "tevent_req_create() failed\n");
417 0 : return NULL;
418 : }
419 :
420 5 : ctx = talloc_get_type(pvt, struct be_ptask_sync_ctx);
421 5 : ret = ctx->fn(mem_ctx, ev, be_ctx, be_ptask, ctx->pvt);
422 :
423 5 : if (ret == EOK) {
424 2 : tevent_req_done(req);
425 : } else {
426 3 : tevent_req_error(req, ret);
427 : }
428 5 : tevent_req_post(req, ev);
429 :
430 5 : return req;
431 : }
432 :
433 4 : static errno_t be_ptask_sync_recv(struct tevent_req *req)
434 : {
435 7 : TEVENT_REQ_RETURN_ON_ERROR(req);
436 :
437 1 : return EOK;
438 : }
439 :
440 5 : errno_t be_ptask_create_sync(TALLOC_CTX *mem_ctx,
441 : struct be_ctx *be_ctx,
442 : time_t period,
443 : time_t first_delay,
444 : time_t enabled_delay,
445 : time_t random_offset,
446 : time_t timeout,
447 : enum be_ptask_offline offline,
448 : time_t max_backoff,
449 : be_ptask_sync_t fn,
450 : void *pvt,
451 : const char *name,
452 : struct be_ptask **_task)
453 : {
454 : errno_t ret;
455 5 : struct be_ptask_sync_ctx *ctx = NULL;
456 :
457 5 : ctx = talloc_zero(mem_ctx, struct be_ptask_sync_ctx);
458 5 : if (ctx == NULL) {
459 0 : ret = ENOMEM;
460 0 : goto done;
461 : }
462 :
463 5 : ctx->fn = fn;
464 5 : ctx->pvt = pvt;
465 :
466 5 : ret = be_ptask_create(mem_ctx, be_ctx, period, first_delay,
467 : enabled_delay, random_offset, timeout, offline,
468 : max_backoff, be_ptask_sync_send, be_ptask_sync_recv,
469 : ctx, name, _task);
470 5 : if (ret != EOK) {
471 1 : goto done;
472 : }
473 :
474 4 : talloc_steal(*_task, ctx);
475 :
476 4 : ret = EOK;
477 :
478 : done:
479 5 : if (ret != EOK) {
480 1 : talloc_free(ctx);
481 : }
482 :
483 5 : return ret;
484 : }
|