Allow Asterisk to compile under GCC 4.10
[asterisk/asterisk.git] / res / res_corosync.c
1 /*
2  * Asterisk -- An open source telephony toolkit.
3  *
4  * Copyright (C) 2007, Digium, Inc.
5  * Copyright (C) 2012, Russell Bryant
6  *
7  * Russell Bryant <russell@russellbryant.net>
8  *
9  * See http://www.asterisk.org for more information about
10  * the Asterisk project. Please do not directly contact
11  * any of the maintainers of this project for assistance;
12  * the project provides a web site, mailing lists and IRC
13  * channels for your use.
14  *
15  * This program is free software, distributed under the terms of
16  * the GNU General Public License Version 2. See the LICENSE file
17  * at the top of the source tree.
18  */
19
20 /*!
21  * \file
22  * \author Russell Bryant <russell@russellbryant.net>
23  *
24  * This module is based on and replaces the previous res_ais module.
25  */
26
27 /*** MODULEINFO
28         <depend>corosync</depend>
29         <defaultenabled>no</defaultenabled>
30         <support_level>extended</support_level>
31  ***/
32
33 #include "asterisk.h"
34
35 ASTERISK_FILE_VERSION(__FILE__, "$Revision$");
36
37 #include <corosync/cpg.h>
38 #include <corosync/cfg.h>
39
40 #include "asterisk/module.h"
41 #include "asterisk/logger.h"
42 #include "asterisk/poll-compat.h"
43 #include "asterisk/config.h"
44 #include "asterisk/event.h"
45 #include "asterisk/cli.h"
46 #include "asterisk/devicestate.h"
47
48 AST_RWLOCK_DEFINE_STATIC(event_types_lock);
49
50 static struct {
51         const char *name;
52         struct ast_event_sub *sub;
53         unsigned char publish;
54         unsigned char publish_default;
55         unsigned char subscribe;
56         unsigned char subscribe_default;
57 } event_types[] = {
58         [AST_EVENT_MWI] = { .name = "mwi", },
59         [AST_EVENT_DEVICE_STATE_CHANGE] = { .name = "device_state", },
60         [AST_EVENT_PING] = { .name = "ping", .publish_default = 1, .subscribe_default = 1 },
61 };
62
63 static struct {
64         pthread_t id;
65         int alert_pipe[2];
66         unsigned int stop:1;
67 } dispatch_thread = {
68         .id = AST_PTHREADT_NULL,
69         .alert_pipe = { -1, -1 },
70 };
71
72 static cpg_handle_t cpg_handle;
73 static corosync_cfg_handle_t cfg_handle;
74
75 #ifdef HAVE_COROSYNC_CFG_STATE_TRACK
76 static void cfg_state_track_cb(
77                 corosync_cfg_state_notification_buffer_t *notification_buffer,
78                 cs_error_t error);
79 #endif /* HAVE_COROSYNC_CFG_STATE_TRACK */
80
81 static void cfg_shutdown_cb(corosync_cfg_handle_t cfg_handle,
82                 corosync_cfg_shutdown_flags_t flags);
83
84 static corosync_cfg_callbacks_t cfg_callbacks = {
85 #ifdef HAVE_COROSYNC_CFG_STATE_TRACK
86         .corosync_cfg_state_track_callback = cfg_state_track_cb,
87 #endif /* HAVE_COROSYNC_CFG_STATE_TRACK */
88         .corosync_cfg_shutdown_callback = cfg_shutdown_cb,
89 };
90
91 static void cpg_deliver_cb(cpg_handle_t handle, const struct cpg_name *group_name,
92                 uint32_t nodeid, uint32_t pid, void *msg, size_t msg_len);
93
94 static void cpg_confchg_cb(cpg_handle_t handle, const struct cpg_name *group_name,
95                 const struct cpg_address *member_list, size_t member_list_entries,
96                 const struct cpg_address *left_list, size_t left_list_entries,
97                 const struct cpg_address *joined_list, size_t joined_list_entries);
98
99 static cpg_callbacks_t cpg_callbacks = {
100         .cpg_deliver_fn = cpg_deliver_cb,
101         .cpg_confchg_fn = cpg_confchg_cb,
102 };
103
104 static void ast_event_cb(const struct ast_event *event, void *data);
105
106 #ifdef HAVE_COROSYNC_CFG_STATE_TRACK
107 static void cfg_state_track_cb(
108                 corosync_cfg_state_notification_buffer_t *notification_buffer,
109                 cs_error_t error)
110 {
111 }
112 #endif /* HAVE_COROSYNC_CFG_STATE_TRACK */
113
114 static void cfg_shutdown_cb(corosync_cfg_handle_t cfg_handle,
115                 corosync_cfg_shutdown_flags_t flags)
116 {
117 }
118
119 static void cpg_deliver_cb(cpg_handle_t handle, const struct cpg_name *group_name,
120                 uint32_t nodeid, uint32_t pid, void *msg, size_t msg_len)
121 {
122         struct ast_event *event;
123
124         if (msg_len < ast_event_minimum_length()) {
125                 ast_debug(1, "Ignoring event that's too small. %u < %u\n",
126                         (unsigned int) msg_len,
127                         (unsigned int) ast_event_minimum_length());
128                 return;
129         }
130
131         if (!ast_eid_cmp(&ast_eid_default, ast_event_get_ie_raw(msg, AST_EVENT_IE_EID))) {
132                 /* Don't feed events back in that originated locally. */
133                 return;
134         }
135
136         ast_rwlock_rdlock(&event_types_lock);
137         if (!event_types[ast_event_get_type(msg)].subscribe) {
138                 /* We are not configured to subscribe to these events. */
139                 ast_rwlock_unlock(&event_types_lock);
140                 return;
141         }
142         ast_rwlock_unlock(&event_types_lock);
143
144         if (!(event = ast_malloc(msg_len))) {
145                 return;
146         }
147
148         memcpy(event, msg, msg_len);
149
150         if (ast_event_get_type(event) == AST_EVENT_PING) {
151                 const struct ast_eid *eid;
152                 char buf[128] = "";
153
154                 eid = ast_event_get_ie_raw(event, AST_EVENT_IE_EID);
155                 ast_eid_to_str(buf, sizeof(buf), (struct ast_eid *) eid);
156                 ast_log(LOG_NOTICE, "(cpg_deliver_cb) Got event PING from server with EID: '%s'\n", buf);
157
158                 ast_event_queue(event);
159         } else {
160                 ast_event_queue_and_cache(event);
161         }
162 }
163
164 static void cpg_confchg_cb(cpg_handle_t handle, const struct cpg_name *group_name,
165                 const struct cpg_address *member_list, size_t member_list_entries,
166                 const struct cpg_address *left_list, size_t left_list_entries,
167                 const struct cpg_address *joined_list, size_t joined_list_entries)
168 {
169         unsigned int i;
170
171         /* If any new nodes have joined, dump our cache of events we are publishing
172          * that originated from this server. */
173
174         if (!joined_list_entries) {
175                 return;
176         }
177
178         for (i = 0; i < ARRAY_LEN(event_types); i++) {
179                 struct ast_event_sub *event_sub;
180
181                 ast_rwlock_rdlock(&event_types_lock);
182                 if (!event_types[i].publish) {
183                         ast_rwlock_unlock(&event_types_lock);
184                         continue;
185                 }
186                 ast_rwlock_unlock(&event_types_lock);
187
188                 event_sub = ast_event_subscribe_new(i, ast_event_cb, NULL);
189                 ast_event_sub_append_ie_raw(event_sub, AST_EVENT_IE_EID,
190                                         &ast_eid_default, sizeof(ast_eid_default));
191                 ast_event_dump_cache(event_sub);
192                 ast_event_sub_destroy(event_sub);
193         }
194 }
195
196 static void *dispatch_thread_handler(void *data)
197 {
198         cs_error_t cs_err;
199         struct pollfd pfd[3] = {
200                 { .events = POLLIN, },
201                 { .events = POLLIN, },
202                 { .events = POLLIN, },
203         };
204
205         if ((cs_err = cpg_fd_get(cpg_handle, &pfd[0].fd)) != CS_OK) {
206                 ast_log(LOG_ERROR, "Failed to get CPG fd.  This module is now broken.\n");
207                 return NULL;
208         }
209
210         if ((cs_err = corosync_cfg_fd_get(cfg_handle, &pfd[1].fd)) != CS_OK) {
211                 ast_log(LOG_ERROR, "Failed to get CFG fd.  This module is now broken.\n");
212                 return NULL;
213         }
214
215         pfd[2].fd = dispatch_thread.alert_pipe[0];
216
217         while (!dispatch_thread.stop) {
218                 int res;
219
220                 cs_err = CS_OK;
221
222                 pfd[0].revents = 0;
223                 pfd[1].revents = 0;
224                 pfd[2].revents = 0;
225
226                 res = ast_poll(pfd, ARRAY_LEN(pfd), -1);
227                 if (res == -1 && errno != EINTR && errno != EAGAIN) {
228                         ast_log(LOG_ERROR, "poll() error: %s (%d)\n", strerror(errno), errno);
229                         continue;
230                 }
231
232                 if (pfd[0].revents & POLLIN) {
233                         if ((cs_err = cpg_dispatch(cpg_handle, CS_DISPATCH_ALL)) != CS_OK) {
234                                 ast_log(LOG_WARNING, "Failed CPG dispatch: %u\n", cs_err);
235                         }
236                 }
237
238                 if (pfd[1].revents & POLLIN) {
239                         if ((cs_err = corosync_cfg_dispatch(cfg_handle, CS_DISPATCH_ALL)) != CS_OK) {
240                                 ast_log(LOG_WARNING, "Failed CFG dispatch: %u\n", cs_err);
241                         }
242                 }
243
244                 if (cs_err == CS_ERR_LIBRARY || cs_err == CS_ERR_BAD_HANDLE) {
245                         struct cpg_name name;
246
247                         /* If corosync gets restarted out from under Asterisk, try to recover. */
248
249                         ast_log(LOG_NOTICE, "Attempting to recover from corosync failure.\n");
250
251                         if ((cs_err = corosync_cfg_initialize(&cfg_handle, &cfg_callbacks)) != CS_OK) {
252                                 ast_log(LOG_ERROR, "Failed to initialize cfg (%d)\n", (int) cs_err);
253                                 sleep(5);
254                                 continue;
255                         }
256
257                         if ((cs_err = cpg_initialize(&cpg_handle, &cpg_callbacks) != CS_OK)) {
258                                 ast_log(LOG_ERROR, "Failed to initialize cpg (%d)\n", (int) cs_err);
259                                 sleep(5);
260                                 continue;
261                         }
262
263                         if ((cs_err = cpg_fd_get(cpg_handle, &pfd[0].fd)) != CS_OK) {
264                                 ast_log(LOG_ERROR, "Failed to get CPG fd.\n");
265                                 sleep(5);
266                                 continue;
267                         }
268
269                         if ((cs_err = corosync_cfg_fd_get(cfg_handle, &pfd[1].fd)) != CS_OK) {
270                                 ast_log(LOG_ERROR, "Failed to get CFG fd.\n");
271                                 sleep(5);
272                                 continue;
273                         }
274
275                         ast_copy_string(name.value, "asterisk", sizeof(name.value));
276                         name.length = strlen(name.value);
277                         if ((cs_err = cpg_join(cpg_handle, &name)) != CS_OK) {
278                                 ast_log(LOG_ERROR, "Failed to join cpg (%d)\n", (int) cs_err);
279                                 sleep(5);
280                                 continue;
281                         }
282
283                         ast_log(LOG_NOTICE, "Corosync recovery complete.\n");
284                 }
285         }
286
287         return NULL;
288 }
289
290 static void ast_event_cb(const struct ast_event *event, void *data)
291 {
292         cs_error_t cs_err;
293         struct iovec iov = {
294                 .iov_base = (void *) event,
295                 .iov_len = ast_event_get_size(event),
296         };
297
298         if (ast_event_get_type(event) == AST_EVENT_PING) {
299                 const struct ast_eid *eid;
300                 char buf[128] = "";
301
302                 eid = ast_event_get_ie_raw(event, AST_EVENT_IE_EID);
303                 ast_eid_to_str(buf, sizeof(buf), (struct ast_eid *) eid);
304                 ast_log(LOG_NOTICE, "(ast_event_cb) Got event PING from server with EID: '%s'\n", buf);
305         }
306
307         if (ast_eid_cmp(&ast_eid_default,
308                         ast_event_get_ie_raw(event, AST_EVENT_IE_EID))) {
309                 /* If the event didn't originate from this server, don't send it back out. */
310                 return;
311         }
312
313         /* The ast_event subscription will only exist if we are configured to publish
314          * these events, so just send away. */
315
316         if ((cs_err = cpg_mcast_joined(cpg_handle, CPG_TYPE_FIFO, &iov, 1)) != CS_OK) {
317                 ast_log(LOG_WARNING, "CPG mcast failed (%u)\n", cs_err);
318         }
319 }
320
321 static char *corosync_show_members(struct ast_cli_entry *e, int cmd, struct ast_cli_args *a)
322 {
323         cs_error_t cs_err;
324         cpg_iteration_handle_t cpg_iter;
325         struct cpg_iteration_description_t cpg_desc;
326         unsigned int i;
327
328         switch (cmd) {
329         case CLI_INIT:
330                 e->command = "corosync show members";
331                 e->usage =
332                         "Usage: corosync show members\n"
333                         "       Show corosync cluster members\n";
334                 return NULL;
335
336         case CLI_GENERATE:
337                 return NULL;    /* no completion */
338         }
339
340         if (a->argc != e->args) {
341                 return CLI_SHOWUSAGE;
342         }
343
344         cs_err = cpg_iteration_initialize(cpg_handle, CPG_ITERATION_ALL, NULL, &cpg_iter);
345
346         if (cs_err != CS_OK) {
347                 ast_cli(a->fd, "Failed to initialize CPG iterator.\n");
348                 return CLI_FAILURE;
349         }
350
351         ast_cli(a->fd, "\n"
352                     "=============================================================\n"
353                     "=== Cluster members =========================================\n"
354                     "=============================================================\n"
355                     "===\n");
356
357         for (i = 1, cs_err = cpg_iteration_next(cpg_iter, &cpg_desc);
358                         cs_err == CS_OK;
359                         cs_err = cpg_iteration_next(cpg_iter, &cpg_desc), i++) {
360                 corosync_cfg_node_address_t addrs[8];
361                 int num_addrs = 0;
362                 unsigned int j;
363
364                 cs_err = corosync_cfg_get_node_addrs(cfg_handle, cpg_desc.nodeid,
365                                 ARRAY_LEN(addrs), &num_addrs, addrs);
366                 if (cs_err != CS_OK) {
367                         ast_log(LOG_WARNING, "Failed to get node addresses\n");
368                         continue;
369                 }
370
371                 ast_cli(a->fd, "=== Node %u\n", i);
372                 ast_cli(a->fd, "=== --> Group: %s\n", cpg_desc.group.value);
373
374                 for (j = 0; j < num_addrs; j++) {
375                         struct sockaddr *sa = (struct sockaddr *) addrs[j].address;
376                         size_t sa_len = (size_t) addrs[j].address_length;
377                         char buf[128];
378
379                         getnameinfo(sa, sa_len, buf, sizeof(buf), NULL, 0, NI_NUMERICHOST);
380
381                         ast_cli(a->fd, "=== --> Address %u: %s\n", j + 1, buf);
382                 }
383
384         }
385
386         ast_cli(a->fd, "===\n"
387                        "=============================================================\n"
388                        "\n");
389
390         cpg_iteration_finalize(cpg_iter);
391
392         return CLI_SUCCESS;
393 }
394
395 static char *corosync_ping(struct ast_cli_entry *e, int cmd, struct ast_cli_args *a)
396 {
397         struct ast_event *event;
398
399         switch (cmd) {
400         case CLI_INIT:
401                 e->command = "corosync ping";
402                 e->usage =
403                         "Usage: corosync ping\n"
404                         "       Send a test ping to the cluster.\n"
405                         "A NOTICE will be in the log for every ping received\n"
406                         "on a server.\n  If you send a ping, you should see a NOTICE\n"
407                         "in the log for every server in the cluster.\n";
408                 return NULL;
409
410         case CLI_GENERATE:
411                 return NULL;    /* no completion */
412         }
413
414         if (a->argc != e->args) {
415                 return CLI_SHOWUSAGE;
416         }
417
418         event = ast_event_new(AST_EVENT_PING, AST_EVENT_IE_END);
419
420         if (!event) {
421                 return CLI_FAILURE;
422         }
423
424         ast_event_queue(event);
425
426         return CLI_SUCCESS;
427 }
428
429 static char *corosync_show_config(struct ast_cli_entry *e, int cmd, struct ast_cli_args *a)
430 {
431         unsigned int i;
432
433         switch (cmd) {
434         case CLI_INIT:
435                 e->command = "corosync show config";
436                 e->usage =
437                         "Usage: corosync show config\n"
438                         "       Show configuration loaded from res_corosync.conf\n";
439                 return NULL;
440
441         case CLI_GENERATE:
442                 return NULL;    /* no completion */
443         }
444
445         if (a->argc != e->args) {
446                 return CLI_SHOWUSAGE;
447         }
448
449         ast_cli(a->fd, "\n"
450                     "=============================================================\n"
451                     "=== res_corosync config =====================================\n"
452                     "=============================================================\n"
453                     "===\n");
454
455         ast_rwlock_rdlock(&event_types_lock);
456         for (i = 0; i < ARRAY_LEN(event_types); i++) {
457                 if (event_types[i].publish) {
458                         ast_cli(a->fd, "=== ==> Publishing Event Type: %s\n",
459                                         event_types[i].name);
460                 }
461                 if (event_types[i].subscribe) {
462                         ast_cli(a->fd, "=== ==> Subscribing to Event Type: %s\n",
463                                         event_types[i].name);
464                 }
465         }
466         ast_rwlock_unlock(&event_types_lock);
467
468         ast_cli(a->fd, "===\n"
469                        "=============================================================\n"
470                        "\n");
471
472         return CLI_SUCCESS;
473 }
474
475 static struct ast_cli_entry corosync_cli[] = {
476         AST_CLI_DEFINE(corosync_show_config, "Show configuration"),
477         AST_CLI_DEFINE(corosync_show_members, "Show cluster members"),
478         AST_CLI_DEFINE(corosync_ping, "Send a test ping to the cluster"),
479 };
480
481 enum {
482         PUBLISH,
483         SUBSCRIBE,
484 };
485
486 static int set_event(const char *event_type, int pubsub)
487 {
488         unsigned int i;
489
490         for (i = 0; i < ARRAY_LEN(event_types); i++) {
491                 if (!event_types[i].name || strcasecmp(event_type, event_types[i].name)) {
492                         continue;
493                 }
494
495                 switch (pubsub) {
496                 case PUBLISH:
497                         event_types[i].publish = 1;
498                         break;
499                 case SUBSCRIBE:
500                         event_types[i].subscribe = 1;
501                         break;
502                 }
503
504                 break;
505         }
506
507         return (i == ARRAY_LEN(event_types)) ? -1 : 0;
508 }
509
510 static int load_general_config(struct ast_config *cfg)
511 {
512         struct ast_variable *v;
513         int res = 0;
514         unsigned int i;
515
516         ast_rwlock_wrlock(&event_types_lock);
517
518         for (i = 0; i < ARRAY_LEN(event_types); i++) {
519                 event_types[i].publish = event_types[i].publish_default;
520                 event_types[i].subscribe = event_types[i].subscribe_default;
521         }
522
523         for (v = ast_variable_browse(cfg, "general"); v && !res; v = v->next) {
524                 if (!strcasecmp(v->name, "publish_event")) {
525                         res = set_event(v->value, PUBLISH);
526                 } else if (!strcasecmp(v->name, "subscribe_event")) {
527                         res = set_event(v->value, SUBSCRIBE);
528                 } else {
529                         ast_log(LOG_WARNING, "Unknown option '%s'\n", v->name);
530                 }
531         }
532
533         for (i = 0; i < ARRAY_LEN(event_types); i++) {
534                 if (event_types[i].publish && !event_types[i].sub) {
535                         event_types[i].sub = ast_event_subscribe(i,
536                                                 ast_event_cb, "Corosync", NULL,
537                                                 AST_EVENT_IE_END);
538                 } else if (!event_types[i].publish && event_types[i].sub) {
539                         event_types[i].sub = ast_event_unsubscribe(event_types[i].sub);
540                 }
541         }
542
543         ast_rwlock_unlock(&event_types_lock);
544
545         return res;
546 }
547
548 static int load_config(unsigned int reload)
549 {
550         static const char filename[] = "res_corosync.conf";
551         struct ast_config *cfg;
552         const char *cat = NULL;
553         struct ast_flags config_flags = { 0 };
554         int res = 0;
555
556         cfg = ast_config_load(filename, config_flags);
557
558         if (cfg == CONFIG_STATUS_FILEMISSING || cfg == CONFIG_STATUS_FILEINVALID) {
559                 return -1;
560         }
561
562         while ((cat = ast_category_browse(cfg, cat))) {
563                 if (!strcasecmp(cat, "general")) {
564                         res = load_general_config(cfg);
565                 } else {
566                         ast_log(LOG_WARNING, "Unknown configuration section '%s'\n", cat);
567                 }
568         }
569
570         ast_config_destroy(cfg);
571
572         return res;
573 }
574
575 static void cleanup_module(void)
576 {
577         cs_error_t cs_err;
578         unsigned int i;
579
580         for (i = 0; i < ARRAY_LEN(event_types); i++) {
581                 if (event_types[i].sub) {
582                         event_types[i].sub = ast_event_unsubscribe(event_types[i].sub);
583                 }
584                 event_types[i].publish = 0;
585                 event_types[i].subscribe = 0;
586         }
587
588         if (dispatch_thread.id != AST_PTHREADT_NULL) {
589                 char meepmeep = 'x';
590                 dispatch_thread.stop = 1;
591                 if (ast_carefulwrite(dispatch_thread.alert_pipe[1], &meepmeep, 1,
592                                         5000) == -1) {
593                         ast_log(LOG_ERROR, "Failed to write to pipe: %s (%d)\n",
594                                         strerror(errno), errno);
595                 }
596                 pthread_join(dispatch_thread.id, NULL);
597         }
598
599         if (dispatch_thread.alert_pipe[0] != -1) {
600                 close(dispatch_thread.alert_pipe[0]);
601                 dispatch_thread.alert_pipe[0] = -1;
602         }
603
604         if (dispatch_thread.alert_pipe[1] != -1) {
605                 close(dispatch_thread.alert_pipe[1]);
606                 dispatch_thread.alert_pipe[1] = -1;
607         }
608
609         if (cpg_handle && (cs_err = cpg_finalize(cpg_handle)) != CS_OK) {
610                 ast_log(LOG_ERROR, "Failed to finalize cpg (%d)\n", (int) cs_err);
611         }
612         cpg_handle = 0;
613
614         if (cfg_handle && (cs_err = corosync_cfg_finalize(cfg_handle)) != CS_OK) {
615                 ast_log(LOG_ERROR, "Failed to finalize cfg (%d)\n", (int) cs_err);
616         }
617         cfg_handle = 0;
618 }
619
620 static int load_module(void)
621 {
622         cs_error_t cs_err;
623         enum ast_module_load_result res = AST_MODULE_LOAD_FAILURE;
624         struct cpg_name name;
625
626         if ((cs_err = corosync_cfg_initialize(&cfg_handle, &cfg_callbacks)) != CS_OK) {
627                 ast_log(LOG_ERROR, "Failed to initialize cfg (%d)\n", (int) cs_err);
628                 return AST_MODULE_LOAD_DECLINE;
629         }
630
631         if ((cs_err = cpg_initialize(&cpg_handle, &cpg_callbacks)) != CS_OK) {
632                 ast_log(LOG_ERROR, "Failed to initialize cpg (%d)\n", (int) cs_err);
633                 goto failed;
634         }
635
636         ast_copy_string(name.value, "asterisk", sizeof(name.value));
637         name.length = strlen(name.value);
638
639         if ((cs_err = cpg_join(cpg_handle, &name)) != CS_OK) {
640                 ast_log(LOG_ERROR, "Failed to join (%d)\n", (int) cs_err);
641                 goto failed;
642         }
643
644         if (pipe(dispatch_thread.alert_pipe) == -1) {
645                 ast_log(LOG_ERROR, "Failed to create alert pipe: %s (%d)\n",
646                                 strerror(errno), errno);
647                 goto failed;
648         }
649
650         if (ast_pthread_create_background(&dispatch_thread.id, NULL,
651                         dispatch_thread_handler, NULL)) {
652                 ast_log(LOG_ERROR, "Error starting CPG dispatch thread.\n");
653                 goto failed;
654         }
655
656         if (load_config(0)) {
657                 /* simply not configured is not a fatal error */
658                 res = AST_MODULE_LOAD_DECLINE;
659                 goto failed;
660         }
661
662         ast_cli_register_multiple(corosync_cli, ARRAY_LEN(corosync_cli));
663
664         return AST_MODULE_LOAD_SUCCESS;
665
666 failed:
667         cleanup_module();
668
669         return res;
670 }
671
672 static int unload_module(void)
673 {
674         ast_cli_unregister_multiple(corosync_cli, ARRAY_LEN(corosync_cli));
675
676         cleanup_module();
677
678         return 0;
679 }
680
681 AST_MODULE_INFO_STANDARD(ASTERISK_GPL_KEY, "Corosync");