• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

eclipse-bluechi / bluechi / 16743008054

05 Aug 2025 07:02AM UTC coverage: 78.083% (+7.4%) from 70.652%
16743008054

push

github

engelmi
Added pypi release section to maintainer readme

Signed-off-by: Michael Engel <mengel@redhat.com>

5369 of 6876 relevant lines covered (78.08%)

991.0 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

83.03
/src/controller/controller.c
1
/*
2
 * Copyright Contributors to the Eclipse BlueChi project
3
 *
4
 * SPDX-License-Identifier: LGPL-2.1-or-later
5
 */
6
#include <string.h>
7
#include <systemd/sd-daemon.h>
8

9
#include "libbluechi/bus/bus.h"
10
#include "libbluechi/bus/utils.h"
11
#include "libbluechi/common/cfg.h"
12
#include "libbluechi/common/common.h"
13
#include "libbluechi/common/event-util.h"
14
#include "libbluechi/common/parse-util.h"
15
#include "libbluechi/common/time-util.h"
16
#include "libbluechi/log/log.h"
17
#include "libbluechi/service/shutdown.h"
18

19
#include "controller.h"
20
#include "job.h"
21
#include "metrics.h"
22
#include "monitor.h"
23
#include "node.h"
24

25
#define DEBUG_MESSAGES 0
26

27
Controller *controller_new(void) {
69✔
28
        int r = 0;
69✔
29
        _cleanup_sd_event_ sd_event *event = NULL;
69✔
30
        r = sd_event_default(&event);
69✔
31
        if (r < 0) {
69✔
32
                bc_log_errorf("Failed to create event loop: %s", strerror(-r));
×
33
                return NULL;
34
        }
35

36
        _cleanup_free_ char *service_name = strdup(BC_DBUS_NAME);
69✔
37
        if (service_name == NULL) {
69✔
38
                bc_log_error("Out of memory");
×
39
                return NULL;
40
        }
41

42
        _cleanup_free_ SocketOptions *socket_opts = socket_options_new();
207✔
43
        if (socket_opts == NULL) {
69✔
44
                bc_log_error("Out of memory");
×
45
                return NULL;
46
        }
47

48
        Controller *controller = malloc0(sizeof(Controller));
69✔
49
        if (controller != NULL) {
69✔
50
                controller->ref_count = 1;
69✔
51
                controller->api_bus_service_name = steal_pointer(&service_name);
69✔
52
                controller->event = steal_pointer(&event);
69✔
53
                controller->metrics_enabled = false;
69✔
54
                controller->number_of_nodes = 0;
69✔
55
                controller->number_of_nodes_online = 0;
69✔
56
                controller->peer_socket_options = steal_pointer(&socket_opts);
69✔
57
                controller->node_connection_tcp_socket_source = NULL;
69✔
58
                controller->node_connection_uds_socket_source = NULL;
69✔
59
                controller->node_connection_systemd_socket_source = NULL;
69✔
60
                LIST_HEAD_INIT(controller->nodes);
69✔
61
                LIST_HEAD_INIT(controller->anonymous_nodes);
69✔
62
                LIST_HEAD_INIT(controller->jobs);
69✔
63
                LIST_HEAD_INIT(controller->monitors);
69✔
64
                LIST_HEAD_INIT(controller->all_subscriptions);
69✔
65
        }
66

67
        return controller;
68
}
69

70
void controller_unref(Controller *controller) {
69✔
71
        assert(controller->ref_count > 0);
69✔
72

73
        controller->ref_count--;
69✔
74
        if (controller->ref_count != 0) {
69✔
75
                return;
76
        }
77

78
        bc_log_debug("Finalizing controller");
69✔
79

80
        /* These are removed in controller_stop */
81
        assert(LIST_IS_EMPTY(controller->jobs));
69✔
82
        assert(LIST_IS_EMPTY(controller->all_subscriptions));
69✔
83
        assert(LIST_IS_EMPTY(controller->monitors));
69✔
84
        assert(LIST_IS_EMPTY(controller->nodes));
69✔
85
        assert(LIST_IS_EMPTY(controller->anonymous_nodes));
69✔
86

87
        if (controller->config) {
69✔
88
                cfg_dispose(controller->config);
69✔
89
                controller->config = NULL;
69✔
90
        }
91

92
        sd_event_unrefp(&controller->event);
69✔
93

94
        free_and_null(controller->api_bus_service_name);
69✔
95
        free_and_null(controller->peer_socket_options);
69✔
96

97
        if (controller->node_connection_tcp_socket_source != NULL) {
69✔
98
                sd_event_source_unrefp(&controller->node_connection_tcp_socket_source);
58✔
99
                controller->node_connection_tcp_socket_source = NULL;
58✔
100
        }
101
        if (controller->node_connection_uds_socket_source != NULL) {
69✔
102
                sd_event_source_unrefp(&controller->node_connection_uds_socket_source);
57✔
103
                controller->node_connection_uds_socket_source = NULL;
57✔
104

105
                /* Remove UDS socket for proper cleanup and not cause an address in use error */
106
                unlink(CONFIG_H_UDS_SOCKET_PATH);
57✔
107
        }
108
        if (controller->node_connection_systemd_socket_source != NULL) {
69✔
109
                sd_event_source_unrefp(&controller->node_connection_systemd_socket_source);
1✔
110
                controller->node_connection_systemd_socket_source = NULL;
1✔
111

112
                /* Remove UDS socket for proper cleanup and not cause an address in use error even
113
                 * though the systemd socket might have been changed to listen on different socket
114
                 */
115
                unlink(CONFIG_H_UDS_SOCKET_PATH);
1✔
116
        }
117

118
        sd_bus_slot_unrefp(&controller->name_owner_changed_slot);
69✔
119
        sd_bus_slot_unrefp(&controller->filter_slot);
69✔
120
        sd_bus_slot_unrefp(&controller->controller_slot);
69✔
121
        sd_bus_slot_unrefp(&controller->metrics_slot);
69✔
122
        sd_bus_unrefp(&controller->api_bus);
69✔
123

124
        free(controller);
69✔
125
}
126

127
void controller_add_subscription(Controller *controller, Subscription *sub) {
13✔
128
        Node *node = NULL;
13✔
129

130
        LIST_APPEND(all_subscriptions, controller->all_subscriptions, subscription_ref(sub));
13✔
131

132
        if (subscription_has_node_wildcard(sub)) {
13✔
133
                LIST_FOREACH(nodes, node, controller->nodes) {
7✔
134
                        node_subscribe(node, sub);
5✔
135
                }
136
                return;
137
        }
138

139
        node = controller_find_node(controller, sub->node);
11✔
140
        if (node) {
11✔
141
                node_subscribe(node, sub);
11✔
142
        } else {
143
                bc_log_errorf("Warning: Subscription to non-existing node %s", sub->node);
×
144
        }
145
}
146

147
void controller_remove_subscription(Controller *controller, Subscription *sub) {
13✔
148
        Node *node = NULL;
13✔
149

150
        if (subscription_has_node_wildcard(sub)) {
13✔
151
                LIST_FOREACH(nodes, node, controller->nodes) {
7✔
152
                        node_unsubscribe(node, sub);
5✔
153
                }
154
        } else {
155
                node = controller_find_node(controller, sub->node);
11✔
156
                if (node) {
11✔
157
                        node_unsubscribe(node, sub);
11✔
158
                }
159
        }
160

161
        LIST_REMOVE(all_subscriptions, controller->all_subscriptions, sub);
13✔
162
        subscription_unref(sub);
13✔
163
}
13✔
164

165
Node *controller_find_node(Controller *controller, const char *name) {
447✔
166
        Node *node = NULL;
447✔
167

168
        LIST_FOREACH(nodes, node, controller->nodes) {
12,071✔
169
                if (strcmp(node->name, name) == 0) {
11,802✔
170
                        return node;
171
                }
172
        }
173

174
        return NULL;
175
}
176

177

178
Node *controller_find_node_by_path(Controller *controller, const char *path) {
57✔
179
        Node *node = NULL;
57✔
180

181
        LIST_FOREACH(nodes, node, controller->nodes) {
70✔
182
                if (streq(node->object_path, path)) {
70✔
183
                        return node;
184
                }
185
        }
186

187
        return NULL;
188
}
189

190
void controller_remove_node(Controller *controller, Node *node) {
390✔
191
        if (node->name) {
390✔
192
                controller->number_of_nodes--;
268✔
193
                LIST_REMOVE(nodes, controller->nodes, node);
268✔
194
        } else {
195
                LIST_REMOVE(nodes, controller->anonymous_nodes, node);
122✔
196
        }
197

198
        if (node_is_online(node)) {
390✔
199
                node_shutdown(node);
12✔
200
                controller->number_of_nodes_online--;
12✔
201
        }
202
        node_unref(node);
390✔
203
}
390✔
204

205
bool controller_add_job(Controller *controller, Job *job) {
30✔
206
        if (!job_export(job)) {
30✔
207
                return false;
208
        }
209

210
        int r = sd_bus_emit_signal(
30✔
211
                        controller->api_bus,
212
                        BC_CONTROLLER_OBJECT_PATH,
213
                        CONTROLLER_INTERFACE,
214
                        "JobNew",
215
                        "uo",
216
                        job->id,
217
                        job->object_path);
218
        if (r < 0) {
30✔
219
                bc_log_errorf("Failed to emit JobNew signal: %s", strerror(-r));
×
220
                return false;
×
221
        }
222

223
        LIST_APPEND(jobs, controller->jobs, job_ref(job));
30✔
224
        return true;
225
}
226

227
void controller_remove_job(Controller *controller, Job *job, const char *result) {
29✔
228
        int r = sd_bus_emit_signal(
58✔
229
                        controller->api_bus,
230
                        BC_CONTROLLER_OBJECT_PATH,
231
                        CONTROLLER_INTERFACE,
232
                        "JobRemoved",
233
                        "uosss",
234
                        job->id,
235
                        job->object_path,
236
                        job->node->name,
29✔
237
                        job->unit,
238
                        result);
239
        if (r < 0) {
29✔
240
                bc_log_errorf("Warning: Failed to send JobRemoved event: %s", strerror(-r));
×
241
                /* We can't really return a failure here */
242
        }
243

244
        LIST_REMOVE(jobs, controller->jobs, job);
29✔
245
        if (controller->metrics_enabled && streq(job->type, "start")) {
29✔
246
                metrics_produce_job_report(job);
1✔
247
        }
248
        job_unref(job);
29✔
249
}
29✔
250

251
void controller_job_state_changed(Controller *controller, uint32_t job_id, const char *state) {
2✔
252
        JobState new_state = job_state_from_string(state);
2✔
253
        Job *job = NULL;
2✔
254
        LIST_FOREACH(jobs, job, controller->jobs) {
2✔
255
                if (job->id == job_id) {
2✔
256
                        job_set_state(job, new_state);
2✔
257
                        break;
2✔
258
                }
259
        }
260
}
2✔
261

262
void controller_finish_job(Controller *controller, uint32_t job_id, const char *result) {
29✔
263
        Job *job = NULL;
29✔
264
        LIST_FOREACH(jobs, job, controller->jobs) {
29✔
265
                if (job->id == job_id) {
29✔
266
                        if (controller->metrics_enabled) {
29✔
267
                                job->job_end_micros = get_time_micros();
2✔
268
                        }
269
                        controller_remove_job(controller, job, result);
29✔
270
                        break;
29✔
271
                }
272
        }
273
}
29✔
274

275
Node *controller_add_node(Controller *controller, const char *name) {
390✔
276
        _cleanup_node_ Node *node = node_new(controller, name);
390✔
277
        if (node == NULL) {
390✔
278
                return NULL;
279
        }
280

281
        if (name) {
390✔
282
                controller->number_of_nodes++;
268✔
283
                LIST_APPEND(nodes, controller->nodes, node);
11,455✔
284
        } else {
285
                LIST_APPEND(nodes, controller->anonymous_nodes, node);
155✔
286
        }
287

288
        return steal_pointer(&node);
289
}
290

291
bool controller_set_use_tcp(Controller *controller, bool use_tcp) {
64✔
292
        controller->use_tcp = use_tcp;
64✔
293
        return true;
64✔
294
}
295

296
bool controller_set_use_uds(Controller *controller, bool use_uds) {
64✔
297
        controller->use_uds = use_uds;
64✔
298
        return true;
64✔
299
}
300

301

302
bool controller_set_port(Controller *controller, const char *port_s) {
61✔
303
        uint16_t port = 0;
61✔
304

305
        if (!parse_port(port_s, &port)) {
61✔
306
                bc_log_errorf("Invalid port format '%s'", port_s);
1✔
307
                return false;
1✔
308
        }
309
        controller->port = port;
60✔
310
        return true;
60✔
311
}
312

313
bool controller_set_heartbeat_interval(Controller *controller, const char *interval_msec) {
58✔
314
        long interval = 0;
58✔
315

316
        if (!parse_long(interval_msec, &interval)) {
58✔
317
                bc_log_errorf("Invalid heartbeat interval format '%s'", interval_msec);
×
318
                return false;
×
319
        }
320
        controller->heartbeat_interval_msec = interval;
58✔
321
        return true;
58✔
322
}
323

324
bool controller_set_heartbeat_threshold(Controller *controller, const char *threshold_msec) {
58✔
325
        long threshold = 0;
58✔
326

327
        if (!parse_long(threshold_msec, &threshold)) {
58✔
328
                bc_log_errorf("Invalid heartbeat threshold format '%s'", threshold_msec);
×
329
                return false;
×
330
        }
331
        controller->heartbeat_threshold_msec = threshold;
58✔
332
        return true;
58✔
333
}
334

335
bool controller_parse_config(Controller *controller, const char *configfile) {
63✔
336
        int result = 0;
63✔
337

338
        result = cfg_initialize(&controller->config);
63✔
339
        if (result != 0) {
63✔
340
                fprintf(stderr, "Error initializing configuration: '%s'.\n", strerror(-result));
×
341
                return false;
×
342
        }
343

344
        result = cfg_controller_def_conf(controller->config);
63✔
345
        if (result != 0) {
63✔
346
                fprintf(stderr, "Failed to set default settings for controller: %s", strerror(-result));
×
347
                return false;
×
348
        }
349

350
        result = cfg_load_complete_configuration(
63✔
351
                        controller->config,
352
                        CFG_BC_DEFAULT_CONFIG,
353
                        CFG_ETC_BC_CONF,
354
                        CFG_ETC_BC_CONF_DIR,
355
                        configfile);
356
        if (result != 0) {
63✔
357
                return false;
5✔
358
        }
359
        return true;
360
}
361

362
bool controller_apply_config(Controller *controller) {
64✔
363
        if (!controller_set_use_tcp(
64✔
364
                            controller, cfg_get_bool_value(controller->config, CFG_CONTROLLER_USE_TCP))) {
64✔
365
                bc_log_error("Failed to set USE TCP");
×
366
                return false;
64✔
367
        }
368
        if (!controller_set_use_uds(
64✔
369
                            controller, cfg_get_bool_value(controller->config, CFG_CONTROLLER_USE_UDS))) {
64✔
370
                bc_log_error("Failed to set USE UDS");
×
371
                return false;
×
372
        }
373

374
        const char *port = NULL;
64✔
375
        port = cfg_get_value(controller->config, CFG_CONTROLLER_PORT);
64✔
376
        if (port) {
64✔
377
                if (!controller_set_port(controller, port)) {
60✔
378
                        return false;
379
                }
380
        }
381

382
        const char *expected_nodes = cfg_get_value(controller->config, CFG_ALLOWED_NODE_NAMES);
63✔
383
        if (expected_nodes) {
63✔
384
                char *saveptr = NULL;
59✔
385

386
                /* copy string of expected nodes since strtok_r manipulates the string it operates on*/
387
                _cleanup_free_ char *expected_nodes_cpy = NULL;
59✔
388
                copy_str(&expected_nodes_cpy, expected_nodes);
59✔
389

390
                char *name = strtok_r(expected_nodes_cpy, ",", &saveptr);
59✔
391
                while (name != NULL) {
329✔
392
                        if (controller_find_node(controller, name) == NULL) {
270✔
393
                                controller_add_node(controller, name);
268✔
394
                        }
395

396
                        name = strtok_r(NULL, ",", &saveptr);
270✔
397
                }
398
        }
399

400
        _cleanup_freev_ char **sections = cfg_list_sections(controller->config);
126✔
401
        if (sections == NULL) {
63✔
402
                bc_log_error("Failed to list config sections");
×
403
                return false;
404
        }
405
        for (size_t i = 0; sections[i] != NULL; i++) {
129✔
406
                const char *section = sections[i];
66✔
407
                if (!str_has_prefix(section, CFG_SECT_NODE_PREFIX)) {
66✔
408
                        continue;
62✔
409
                }
410
                const char *node_name = section + strlen(CFG_SECT_NODE_PREFIX);
4✔
411
                Node *node = controller_find_node(controller, node_name);
4✔
412
                if (node == NULL) {
4✔
413
                        /* Add it to the list if it is marked allowed */
414
                        bool allowed = cfg_s_get_bool_value(controller->config, section, CFG_ALLOWED);
×
415
                        if (!allowed) {
×
416
                                continue;
×
417
                        }
418
                        node = controller_add_node(controller, node_name);
×
419
                }
420

421
                const char *selinux_context = cfg_s_get_value(
4✔
422
                                controller->config, section, CFG_REQUIRED_SELINUX_CONTEXT);
423
                if (selinux_context && !node_set_required_selinux_context(node, selinux_context)) {
4✔
424
                        return false;
425
                }
426

427
                const char *proxy_enabled_nodes = cfg_s_get_value(
4✔
428
                                controller->config, section, CFG_ALLOW_DEPENDENCIES_ON);
429
                if (proxy_enabled_nodes) {
4✔
430
                        char *saveptr = NULL;
4✔
431

432
                        /* copy string of expected nodes since strtok_r manipulates the string it operates on*/
433
                        _cleanup_free_ char *proxy_enabled_nodes_cpy = NULL;
4✔
434
                        copy_str(&proxy_enabled_nodes_cpy, proxy_enabled_nodes);
4✔
435

436
                        char *name = strtok_r(proxy_enabled_nodes_cpy, ",", &saveptr);
4✔
437
                        while (name != NULL) {
8✔
438
                                if (node_add_allowed_proxy_target(node, name) < 0) {
4✔
439
                                        return false;
×
440
                                }
441

442
                                name = strtok_r(NULL, ",", &saveptr);
4✔
443
                        }
444
                }
445
        }
446

447
        const char *interval_msec = cfg_get_value(controller->config, CFG_HEARTBEAT_INTERVAL);
63✔
448
        if (interval_msec) {
63✔
449
                if (!controller_set_heartbeat_interval(controller, interval_msec)) {
58✔
450
                        return false;
451
                }
452
        }
453

454
        const char *threshold_msec = cfg_get_value(controller->config, CFG_NODE_HEARTBEAT_THRESHOLD);
63✔
455
        if (threshold_msec) {
63✔
456
                if (!controller_set_heartbeat_threshold(controller, threshold_msec)) {
58✔
457
                        return false;
458
                }
459
        }
460

461
        /* Set socket options used for peer connections with the agents */
462
        const char *keepidle = cfg_get_value(controller->config, CFG_TCP_KEEPALIVE_TIME);
63✔
463
        if (keepidle) {
63✔
464
                if (socket_options_set_tcp_keepidle(controller->peer_socket_options, keepidle) < 0) {
60✔
465
                        bc_log_error("Failed to set TCP KEEPIDLE");
1✔
466
                        return false;
467
                }
468
        }
469
        const char *keepintvl = cfg_get_value(controller->config, CFG_TCP_KEEPALIVE_INTERVAL);
62✔
470
        if (keepintvl) {
62✔
471
                if (socket_options_set_tcp_keepintvl(controller->peer_socket_options, keepintvl) < 0) {
60✔
472
                        bc_log_error("Failed to set TCP KEEPINTVL");
1✔
473
                        return false;
474
                }
475
        }
476
        const char *keepcnt = cfg_get_value(controller->config, CFG_TCP_KEEPALIVE_COUNT);
61✔
477
        if (keepcnt) {
61✔
478
                if (socket_options_set_tcp_keepcnt(controller->peer_socket_options, keepcnt) < 0) {
60✔
479
                        bc_log_error("Failed to set TCP KEEPCNT");
1✔
480
                        return false;
481
                }
482
        }
483
        if (socket_options_set_ip_recverr(
60✔
484
                            controller->peer_socket_options,
485
                            cfg_get_bool_value(controller->config, CFG_IP_RECEIVE_ERRORS)) < 0) {
60✔
486
                bc_log_error("Failed to set IP RECVERR");
×
487
                return false;
488
        }
489

490
        return true;
491
}
492

493
static int controller_accept_node_connection(
122✔
494
                UNUSED sd_event_source *source, int fd, UNUSED uint32_t revents, void *userdata) {
495
        Controller *controller = userdata;
122✔
496
        Node *node = NULL;
122✔
497
        _cleanup_fd_ int nfd = accept_connection_request(fd);
122✔
498
        if (nfd < 0) {
122✔
499
                bc_log_errorf("Failed to accept connection request: %s", strerror(-nfd));
×
500
                return -1;
×
501
        }
502

503
        _cleanup_sd_bus_ sd_bus *dbus_server = peer_bus_open_server(
244✔
504
                        controller->event, "managed-node", BC_DBUS_NAME, steal_fd(&nfd));
505
        if (dbus_server == NULL) {
122✔
506
                return -1;
507
        }
508

509
        bus_socket_set_options(dbus_server, controller->peer_socket_options);
122✔
510

511
        /* Add anonymous node */
512
        node = controller_add_node(controller, NULL);
122✔
513
        if (node == NULL) {
122✔
514
                return -1;
515
        }
516

517
        if (!node_set_agent_bus(node, dbus_server)) {
122✔
518
                controller_remove_node(controller, steal_pointer(&node));
×
519
                return -1;
520
        }
521

522
        return 0;
523
}
524

525
static bool controller_setup_systemd_socket_connection_handler(Controller *controller) {
58✔
526
        int r = 0;
58✔
527
        _cleanup_sd_event_source_ sd_event_source *event_source = NULL;
58✔
528

529
        int n = sd_listen_fds(0);
58✔
530
        if (n < 1) {
58✔
531
                bc_log_debug("No socket unit file descriptor has been passed");
57✔
532
                return true;
533
        }
534
        if (n > 1) {
1✔
535
                bc_log_errorf("Received too many file descriptors from socket unit - %d", n);
×
536
                return false;
537
        }
538

539
        r = sd_event_add_io(
1✔
540
                        controller->event,
541
                        &event_source,
542
                        SD_LISTEN_FDS_START,
543
                        EPOLLIN,
544
                        controller_accept_node_connection,
545
                        controller);
546
        if (r < 0) {
1✔
547
                bc_log_errorf("Failed to add io event source for systemd socket unit: %s", strerror(-r));
×
548
                return false;
549
        }
550
        r = sd_event_source_set_io_fd_own(event_source, true);
1✔
551
        if (r < 0) {
1✔
552
                bc_log_errorf("Failed to set io fd own for systemd socket unit: %s", strerror(-r));
×
553
                return false;
554
        }
555

556
        (void) sd_event_source_set_description(event_source, "node-accept-systemd-socket");
1✔
557
        controller->node_connection_systemd_socket_source = steal_pointer(&event_source);
1✔
558

559
        bc_log_info("Waiting for connection requests on configured socket unit...");
1✔
560
        return true;
561
}
562

563
static bool controller_setup_tcp_connection_handler(Controller *controller) {
58✔
564
        int r = 0;
58✔
565
        _cleanup_fd_ int tcp_fd = -1;
58✔
566
        _cleanup_sd_event_source_ sd_event_source *event_source = NULL;
58✔
567

568
        tcp_fd = create_tcp_socket(controller->port);
58✔
569
        if (tcp_fd < 0) {
58✔
570
                /*
571
                 * Check if the address is already in use and if the systemd file descriptor already uses it.
572
                 * In case both conditions are true, only log a warning and proceed as successful since a
573
                 * proper TCP socket incl. handler has already been set up.
574
                 */
575
                if (tcp_fd == -EADDRINUSE && controller->node_connection_systemd_socket_source != NULL &&
×
576
                    sd_is_socket_inet(SD_LISTEN_FDS_START, AF_UNSPEC, SOCK_STREAM, 1, controller->port) > 0) {
×
577
                        bc_log_warnf("TCP socket for port %d already setup with systemd socket unit",
×
578
                                     controller->port);
579
                        return true;
580
                }
581

582
                bc_log_errorf("Failed to create TCP socket: %s", strerror(-tcp_fd));
×
583
                return false;
584
        }
585

586
        r = sd_event_add_io(
58✔
587
                        controller->event,
588
                        &event_source,
589
                        tcp_fd,
590
                        EPOLLIN,
591
                        controller_accept_node_connection,
592
                        controller);
593
        if (r < 0) {
58✔
594
                bc_log_errorf("Failed to add io event for tcp socket: %s", strerror(-r));
×
595
                return false;
596
        }
597
        r = sd_event_source_set_io_fd_own(event_source, true);
58✔
598
        if (r < 0) {
58✔
599
                bc_log_errorf("Failed to set io fd own for tcp socket: %s", strerror(-r));
×
600
                return false;
601
        }
602
        // sd_event_set_io_fd_own takes care of closing tcp_fd
603
        steal_fd(&tcp_fd);
58✔
604

605
        (void) sd_event_source_set_description(event_source, "node-accept-tcp-socket");
58✔
606
        controller->node_connection_tcp_socket_source = steal_pointer(&event_source);
58✔
607

608
        bc_log_infof("Waiting for connection requests on port %d...", controller->port);
58✔
609
        return true;
610
}
611

612

613
static bool controller_setup_uds_connection_handler(Controller *controller) {
58✔
614
        int r = 0;
58✔
615
        _cleanup_fd_ int uds_fd = -1;
58✔
616
        _cleanup_sd_event_source_ sd_event_source *event_source = NULL;
58✔
617

618
        uds_fd = create_uds_socket(CONFIG_H_UDS_SOCKET_PATH);
58✔
619
        if (uds_fd < 0) {
58✔
620
                /*
621
                 * Check if the uds path is already in use and if the systemd file descriptor already uses
622
                 * it. In case both conditions are true, only log a warning and proceed as successful since a
623
                 * proper UDS incl. handler has already been set up.
624
                 */
625
                if (uds_fd == -EADDRINUSE && controller->node_connection_systemd_socket_source != NULL &&
2✔
626
                    sd_is_socket_unix(SD_LISTEN_FDS_START, AF_UNIX, 1, CONFIG_H_UDS_SOCKET_PATH, 0) > 0) {
1✔
627
                        bc_log_warnf("UDS socket for path %s already setup with systemd socket unit",
1✔
628
                                     CONFIG_H_UDS_SOCKET_PATH);
629
                        return true;
630
                } else if (uds_fd == -EADDRINUSE) {
×
631
                        /* If address is in use, remove socket file and retry again */
632
                        unlink(CONFIG_H_UDS_SOCKET_PATH);
×
633
                        uds_fd = create_uds_socket(CONFIG_H_UDS_SOCKET_PATH);
×
634
                        if (uds_fd < 0) {
×
635
                                bc_log_errorf("Failed to create UDS socket: %s", strerror(-uds_fd));
×
636
                                return false;
637
                        }
638
                } else {
639
                        bc_log_errorf("Failed to create UDS socket: %s", strerror(-uds_fd));
×
640
                        return false;
641
                }
642
        }
643

644
        r = sd_event_add_io(
57✔
645
                        controller->event,
646
                        &event_source,
647
                        uds_fd,
648
                        EPOLLIN,
649
                        controller_accept_node_connection,
650
                        controller);
651
        if (r < 0) {
57✔
652
                bc_log_errorf("Failed to add io event for uds socket: %s", strerror(-r));
×
653
                return false;
654
        }
655
        r = sd_event_source_set_io_fd_own(event_source, true);
57✔
656
        if (r < 0) {
57✔
657
                bc_log_errorf("Failed to set io fd own for uds socket: %s", strerror(-r));
×
658
                return false;
659
        }
660
        // sd_event_set_io_fd_own takes care of closing uds_fd
661
        steal_fd(&uds_fd);
57✔
662

663
        (void) sd_event_source_set_description(event_source, "node-accept-uds-socket");
57✔
664
        controller->node_connection_uds_socket_source = steal_pointer(&event_source);
57✔
665

666
        bc_log_infof("Waiting for connection requests on socket %s...", CONFIG_H_UDS_SOCKET_PATH);
57✔
667
        return true;
668
}
669

670

671
static bool controller_setup_node_connection_handler(Controller *controller) {
58✔
672
        if (!controller_setup_systemd_socket_connection_handler(controller)) {
58✔
673
                return false;
674
        }
675
        if (controller->use_tcp && !controller_setup_tcp_connection_handler(controller)) {
58✔
676
                return false;
677
        }
678
        if (controller->use_uds && !controller_setup_uds_connection_handler(controller)) {
58✔
679
                return false;
680
        }
681

682
        if (controller->node_connection_systemd_socket_source == NULL &&
58✔
683
            controller->node_connection_tcp_socket_source == NULL &&
57✔
684
            controller->node_connection_uds_socket_source == NULL) {
×
685
                bc_log_error("No connection request handler configured");
×
686
                return false;
×
687
        }
688
        return true;
689
}
690

691
static int controller_reset_heartbeat_timer(Controller *controller, sd_event_source **event_source);
692

693
static bool controller_check_node_liveness(Controller *controller, Node *node, uint64_t now) {
8✔
694
        uint64_t diff = 0;
8✔
695

696
        if (controller->heartbeat_threshold_msec <= 0) {
8✔
697
                /* checking liveness of node by heartbeat disabled since configured threshold is <=0" */
698
                return true;
699
        }
700

701
        if (now == 0) {
8✔
702
                bc_log_error("Current time is wrong");
×
703
                return true;
×
704
        }
705

706
        if (now < node->last_seen_monotonic) {
8✔
707
                bc_log_error("Clock skew detected");
×
708
                return true;
×
709
        }
710

711
        diff = now - node->last_seen_monotonic;
8✔
712
        if (diff > (uint64_t) controller->heartbeat_threshold_msec * USEC_PER_MSEC) {
8✔
713
                bc_log_infof("Did not receive heartbeat from node '%s' since '%d'ms. Disconnecting it...",
1✔
714
                             node->name,
715
                             controller->heartbeat_threshold_msec);
716
                node_disconnect(node);
1✔
717
                return false;
1✔
718
        }
719

720
        return true;
721
}
722

723
static int controller_heartbeat_timer_callback(
4✔
724
                sd_event_source *event_source, UNUSED uint64_t usec, void *userdata) {
725
        Controller *controller = (Controller *) userdata;
4✔
726
        Node *node = NULL;
4✔
727
        uint64_t now = get_time_micros_monotonic();
4✔
728
        int r = 0;
4✔
729

730
        LIST_FOREACH(nodes, node, controller->nodes) {
12✔
731
                if (!node_is_online(node)) {
8✔
732
                        continue;
×
733
                }
734

735
                if (!controller_check_node_liveness(controller, node, now)) {
8✔
736
                        continue;
1✔
737
                }
738

739
                r = sd_bus_emit_signal(
7✔
740
                                node->agent_bus,
741
                                INTERNAL_CONTROLLER_OBJECT_PATH,
742
                                INTERNAL_CONTROLLER_INTERFACE,
743
                                "Heartbeat",
744
                                "");
745
                if (r < 0) {
7✔
746
                        bc_log_errorf("Failed to emit heartbeat signal to node '%s': %s",
×
747
                                      node->name,
748
                                      strerror(-r));
749
                }
750
        }
751

752
        r = controller_reset_heartbeat_timer(controller, &event_source);
4✔
753
        if (r < 0) {
4✔
754
                bc_log_errorf("Failed to reset controller heartbeat timer: %s", strerror(-r));
×
755
                return r;
×
756
        }
757

758
        return 0;
759
}
760

761
static int controller_reset_heartbeat_timer(Controller *controller, sd_event_source **event_source) {
5✔
762
        return event_reset_time_relative(
10✔
763
                        controller->event,
764
                        event_source,
765
                        CLOCK_BOOTTIME,
766
                        controller->heartbeat_interval_msec * USEC_PER_MSEC,
5✔
767
                        0,
768
                        controller_heartbeat_timer_callback,
769
                        controller,
770
                        0,
771
                        "controller-heartbeat-timer-source",
772
                        false);
773
}
774

775
static int controller_setup_heartbeat_timer(Controller *controller) {
58✔
776
        _cleanup_(sd_event_source_unrefp) sd_event_source *event_source = NULL;
58✔
777
        int r = 0;
58✔
778

779
        assert(controller);
58✔
780

781
        if (controller->heartbeat_interval_msec <= 0) {
58✔
782
                bc_log_warnf("Heartbeat disabled since configured interval '%d' is <=0",
57✔
783
                             controller->heartbeat_interval_msec);
784
                return 0;
785
        }
786

787
        r = controller_reset_heartbeat_timer(controller, &event_source);
1✔
788
        if (r < 0) {
1✔
789
                bc_log_errorf("Failed to reset controller heartbeat timer: %s", strerror(-r));
×
790
                return r;
791
        }
792

793
        return sd_event_source_set_floating(event_source, true);
1✔
794
}
795

796
/************************************************************************
797
 ***************** AgentFleetRequest ************************************
798
 ************************************************************************/
799

800
typedef struct AgentFleetRequest AgentFleetRequest;
801

802
typedef int (*agent_fleet_request_encode_reply_t)(AgentFleetRequest *req, sd_bus_message *reply);
803
typedef AgentRequest *(*agent_fleet_request_create_t)(
804
                Node *node, agent_request_response_t cb, void *userdata, free_func_t free_userdata);
805

806
typedef struct AgentFleetRequest {
807
        sd_bus_message *request_message;
808
        agent_fleet_request_encode_reply_t encode;
809

810
        int n_done;
811
        int n_sub_req;
812
        struct {
813
                Node *node;
814
                sd_bus_message *m;
815
                AgentRequest *agent_req;
816
        } sub_req[0];
817
} AgentFleetRequest;
818

819
static void agent_fleet_request_free(AgentFleetRequest *req) {
2✔
820
        sd_bus_message_unref(req->request_message);
2✔
821

822
        for (int i = 0; i < req->n_sub_req; i++) {
7✔
823
                node_unrefp(&req->sub_req[i].node);
5✔
824
                sd_bus_message_unrefp(&req->sub_req[i].m);
5✔
825
                agent_request_unrefp(&req->sub_req[i].agent_req);
5✔
826
        }
827

828
        free(req);
2✔
829
}
2✔
830

831
static void agent_fleet_request_freep(AgentFleetRequest **reqp) {
2✔
832
        if (reqp && *reqp) {
2✔
833
                agent_fleet_request_free(*reqp);
2✔
834
                *reqp = NULL;
2✔
835
        }
836
}
2✔
837

838
#define _cleanup_agent_fleet_request_ _cleanup_(agent_fleet_request_freep)
839

840
static void agent_fleet_request_done(AgentFleetRequest *req) {
2✔
841
        /* All sub_req-requests are done, collect results and free when done */
842
        UNUSED _cleanup_agent_fleet_request_ AgentFleetRequest *free_me = req;
×
843

844
        _cleanup_sd_bus_message_ sd_bus_message *reply = NULL;
2✔
845
        int r = sd_bus_message_new_method_return(req->request_message, &reply);
2✔
846
        if (r < 0) {
2✔
847
                sd_bus_reply_method_errorf(
×
848
                                req->request_message,
849
                                SD_BUS_ERROR_FAILED,
850
                                "Failed to create a reply message: %s",
851
                                strerror(-r));
852
                return;
853
        }
854

855
        r = req->encode(req, reply);
2✔
856
        if (r < 0) {
2✔
857
                sd_bus_reply_method_errorf(
×
858
                                req->request_message,
859
                                SD_BUS_ERROR_FAILED,
860
                                "Request to at least one node failed: %s",
861
                                strerror(-r));
862
                return;
863
        }
864

865
        r = sd_bus_message_send(reply);
2✔
866
        if (r < 0) {
2✔
867
                bc_log_errorf("Failed to send reply message: %s", strerror(-r));
×
868
                return;
869
        }
870
}
871

872
static void agent_fleet_request_maybe_done(AgentFleetRequest *req) {
7✔
873
        if (req->n_done == req->n_sub_req) {
7✔
874
                agent_fleet_request_done(req);
2✔
875
        }
876
}
7✔
877

878
static int agent_fleet_request_callback(
5✔
879
                AgentRequest *agent_req, sd_bus_message *m, UNUSED sd_bus_error *ret_error) {
880
        AgentFleetRequest *req = agent_req->userdata;
5✔
881
        int i = 0;
5✔
882

883
        for (i = 0; i < req->n_sub_req; i++) {
9✔
884
                if (req->sub_req[i].agent_req == agent_req) {
9✔
885
                        break;
886
                }
887
        }
888

889
        assert(i != req->n_sub_req); /* we should have found the sub_req request */
5✔
890

891
        req->sub_req[i].m = sd_bus_message_ref(m);
5✔
892
        req->n_done++;
5✔
893

894
        agent_fleet_request_maybe_done(req);
5✔
895

896
        return 0;
5✔
897
}
898

899
static int agent_fleet_request_start(
2✔
900
                sd_bus_message *request_message,
901
                Controller *controller,
902
                agent_fleet_request_create_t create_request,
903
                agent_fleet_request_encode_reply_t encode) {
904
        AgentFleetRequest *req = NULL;
2✔
905

906
        req = malloc0_array(sizeof(*req), sizeof(req->sub_req[0]), controller->number_of_nodes);
2✔
907
        if (req == NULL) {
2✔
908
                return sd_bus_reply_method_errorf(request_message, SD_BUS_ERROR_NO_MEMORY, "Out of memory");
×
909
        }
910
        req->request_message = sd_bus_message_ref(request_message);
2✔
911
        req->encode = encode;
2✔
912

913
        Node *node = NULL;
2✔
914
        int i = 0;
2✔
915
        LIST_FOREACH(nodes, node, controller->nodes) {
7✔
916
                _cleanup_agent_request_ AgentRequest *agent_req = create_request(
10✔
917
                                node, agent_fleet_request_callback, req, NULL);
918
                if (agent_req) {
5✔
919
                        req->sub_req[i].agent_req = steal_pointer(&agent_req);
5✔
920
                        req->sub_req[i].node = node_ref(node);
5✔
921
                        req->n_sub_req++;
5✔
922
                        i++;
5✔
923
                }
924
        }
925

926
// Disabling -Wanalyzer-malloc-leak temporarily due to false-positive
927
//      Leak detected is based on the assumption that controller_method_list_units_maybe_done is only
928
//      called once directly after iterating over the list - when the conditional to free req is false.
929
//      However, it does not take into account that controller_list_units_callback calls it for each node.
930
#pragma GCC diagnostic push
931
#pragma GCC diagnostic ignored "-Wanalyzer-malloc-leak"
932

933
        agent_fleet_request_maybe_done(req);
2✔
934

935
        return 1;
2✔
936
}
937
#pragma GCC diagnostic pop
938

939
/************************************************************************
940
 ************** org.eclipse.bluechi.Controller.ListUnits *****
941
 ************************************************************************/
942

943
static int controller_method_list_units_encode_reply(AgentFleetRequest *req, sd_bus_message *reply) {
1✔
944
        int r = sd_bus_message_open_container(reply, SD_BUS_TYPE_ARRAY, NODE_AND_UNIT_INFO_DICT_TYPESTRING);
1✔
945
        if (r < 0) {
1✔
946
                return r;
947
        }
948

949
        for (int i = 0; i < req->n_sub_req; i++) {
3✔
950
                const char *node_name = req->sub_req[i].node->name;
2✔
951
                sd_bus_message *m = req->sub_req[i].m;
2✔
952
                if (m == NULL) {
2✔
953
                        continue;
×
954
                }
955

956
                const sd_bus_error *err = sd_bus_message_get_error(m);
2✔
957
                if (err != NULL) {
2✔
958
                        return -sd_bus_message_get_errno(m);
×
959
                }
960

961
                r = sd_bus_message_open_container(reply, SD_BUS_TYPE_DICT_ENTRY, NODE_AND_UNIT_INFO_TYPESTRING);
2✔
962
                if (r < 0) {
2✔
963
                        return r;
964
                }
965

966
                r = sd_bus_message_append(reply, "s", node_name);
2✔
967
                if (r < 0) {
2✔
968
                        return r;
969
                }
970

971
                r = sd_bus_message_open_container(reply, SD_BUS_TYPE_ARRAY, UNIT_INFO_STRUCT_TYPESTRING);
2✔
972
                if (r < 0) {
2✔
973
                        return r;
974
                }
975

976
                r = sd_bus_message_enter_container(m, SD_BUS_TYPE_ARRAY, UNIT_INFO_STRUCT_TYPESTRING);
2✔
977
                if (r < 0) {
2✔
978
                        return r;
979
                }
980

981
                while (sd_bus_message_at_end(m, false) == 0) {
4✔
982
                        r = sd_bus_message_copy(reply, m, true);
2✔
983
                        if (r < 0) {
2✔
984
                                return r;
985
                        }
986
                }
987

988
                r = sd_bus_message_close_container(reply);
2✔
989
                if (r < 0) {
2✔
990
                        return r;
991
                }
992

993
                r = sd_bus_message_close_container(reply);
2✔
994
                if (r < 0) {
2✔
995
                        return r;
996
                }
997

998
                r = sd_bus_message_exit_container(m);
2✔
999
                if (r < 0) {
2✔
1000
                        return r;
1001
                }
1002
        }
1003

1004
        r = sd_bus_message_close_container(reply);
1✔
1005
        if (r < 0) {
1✔
1006
                return r;
×
1007
        }
1008

1009
        return 0;
1010
}
1011

1012
static int controller_method_list_units(sd_bus_message *m, void *userdata, UNUSED sd_bus_error *ret_error) {
1✔
1013
        Controller *controller = userdata;
1✔
1014
        return agent_fleet_request_start(
1✔
1015
                        m, controller, node_request_list_units, controller_method_list_units_encode_reply);
1016
}
1017

1018
/************************************************************************
1019
 ***** org.eclipse.bluechi.Controller.ListUnitFiles **************
1020
 ************************************************************************/
1021

1022
static int controller_method_list_unit_files_encode_reply(AgentFleetRequest *req, sd_bus_message *reply) {
1✔
1023
        int r = sd_bus_message_open_container(
1✔
1024
                        reply, SD_BUS_TYPE_ARRAY, NODE_AND_UNIT_FILE_INFO_DICT_TYPESTRING);
1025
        if (r < 0) {
1✔
1026
                return r;
1027
        }
1028

1029
        for (int i = 0; i < req->n_sub_req; i++) {
4✔
1030
                const char *node_name = req->sub_req[i].node->name;
3✔
1031
                sd_bus_message *m = req->sub_req[i].m;
3✔
1032
                if (m == NULL) {
3✔
1033
                        continue;
×
1034
                }
1035

1036
                const sd_bus_error *err = sd_bus_message_get_error(m);
3✔
1037
                if (err != NULL) {
3✔
1038
                        bc_log_errorf("Failed to list unit files for node '%s': %s", node_name, err->message);
×
1039
                        return -sd_bus_message_get_errno(m);
×
1040
                }
1041

1042
                r = sd_bus_message_open_container(
3✔
1043
                                reply, SD_BUS_TYPE_DICT_ENTRY, NODE_AND_UNIT_FILE_INFO_TYPESTRING);
1044
                if (r < 0) {
3✔
1045
                        return r;
1046
                }
1047

1048
                r = sd_bus_message_append(reply, "s", node_name);
3✔
1049
                if (r < 0) {
3✔
1050
                        return r;
1051
                }
1052

1053
                r = sd_bus_message_open_container(reply, SD_BUS_TYPE_ARRAY, UNIT_FILE_INFO_STRUCT_TYPESTRING);
3✔
1054
                if (r < 0) {
3✔
1055
                        return r;
1056
                }
1057

1058
                r = sd_bus_message_enter_container(m, SD_BUS_TYPE_ARRAY, UNIT_FILE_INFO_STRUCT_TYPESTRING);
3✔
1059
                if (r < 0) {
3✔
1060
                        return r;
1061
                }
1062

1063
                while (sd_bus_message_at_end(m, false) == 0) {
6✔
1064
                        r = sd_bus_message_copy(reply, m, true);
3✔
1065
                        if (r < 0) {
3✔
1066
                                return r;
1067
                        }
1068
                }
1069

1070
                r = sd_bus_message_close_container(reply);
3✔
1071
                if (r < 0) {
3✔
1072
                        return r;
1073
                }
1074

1075
                r = sd_bus_message_close_container(reply);
3✔
1076
                if (r < 0) {
3✔
1077
                        return r;
1078
                }
1079

1080
                r = sd_bus_message_exit_container(m);
3✔
1081
                if (r < 0) {
3✔
1082
                        return r;
1083
                }
1084
        }
1085

1086
        r = sd_bus_message_close_container(reply);
1✔
1087
        if (r < 0) {
1✔
1088
                return r;
×
1089
        }
1090

1091
        return 0;
1092
}
1093

1094
static int controller_method_list_unit_files(sd_bus_message *m, void *userdata, UNUSED sd_bus_error *ret_error) {
1✔
1095
        Controller *controller = userdata;
1✔
1096
        return agent_fleet_request_start(
1✔
1097
                        m,
1098
                        controller,
1099
                        node_request_list_unit_files,
1100
                        controller_method_list_unit_files_encode_reply);
1101
}
1102

1103
/************************************************************************
1104
 ***** org.eclipse.bluechi.Controller.ListNodes **************
1105
 ************************************************************************/
1106

1107
static int controller_method_list_encode_node(sd_bus_message *reply, Node *node) {
21✔
1108
        int r = sd_bus_message_open_container(reply, SD_BUS_TYPE_STRUCT, "soss");
21✔
1109
        if (r < 0) {
21✔
1110
                return r;
1111
        }
1112

1113
        r = sd_bus_message_append(
21✔
1114
                        reply, "soss", node->name, node->object_path, node_get_status(node), node->peer_ip);
1115
        if (r < 0) {
21✔
1116
                return r;
1117
        }
1118
        return sd_bus_message_close_container(reply);
21✔
1119
}
1120

1121
static int controller_method_list_nodes(sd_bus_message *m, void *userdata, UNUSED sd_bus_error *ret_error) {
11✔
1122
        Controller *controller = userdata;
11✔
1123
        _cleanup_sd_bus_message_ sd_bus_message *reply = NULL;
11✔
1124
        Node *node = NULL;
11✔
1125

1126
        int r = sd_bus_message_new_method_return(m, &reply);
11✔
1127
        if (r < 0) {
11✔
1128
                return sd_bus_reply_method_errorf(
×
1129
                                reply,
1130
                                SD_BUS_ERROR_FAILED,
1131
                                "Failed to create a reply message: %s",
1132
                                strerror(-r));
1133
        }
1134

1135
        r = sd_bus_message_open_container(reply, SD_BUS_TYPE_ARRAY, "(soss)");
11✔
1136
        if (r < 0) {
11✔
1137
                return sd_bus_reply_method_errorf(
×
1138
                                reply,
1139
                                SD_BUS_ERROR_INVALID_ARGS,
1140
                                "Invalid argument for the reply message: %s",
1141
                                strerror(-r));
1142
        }
1143

1144
        LIST_FOREACH(nodes, node, controller->nodes) {
32✔
1145
                r = controller_method_list_encode_node(reply, node);
21✔
1146
                if (r < 0) {
21✔
1147
                        return sd_bus_reply_method_errorf(
×
1148
                                        reply, SD_BUS_ERROR_FAILED, "Failed to encode a node: %s", strerror(-r));
1149
                }
1150
        }
1151

1152
        r = sd_bus_message_close_container(reply);
11✔
1153
        if (r < 0) {
11✔
1154
                return sd_bus_reply_method_errorf(
×
1155
                                reply, SD_BUS_ERROR_FAILED, "Failed to close message: %s", strerror(-r));
1156
        }
1157

1158
        return sd_bus_message_send(reply);
11✔
1159
}
1160

1161
/************************************************************************
1162
 **** org.eclipse.bluechi.Controller.GetNode *****************
1163
 ************************************************************************/
1164

1165
static int controller_method_get_node(sd_bus_message *m, void *userdata, UNUSED sd_bus_error *ret_error) {
24✔
1166
        Controller *controller = userdata;
24✔
1167
        _cleanup_sd_bus_message_ sd_bus_message *reply = NULL;
24✔
1168
        Node *node = NULL;
24✔
1169
        const char *node_name = NULL;
24✔
1170

1171
        int r = sd_bus_message_read(m, "s", &node_name);
24✔
1172
        if (r < 0) {
24✔
1173
                return sd_bus_reply_method_errorf(
×
1174
                                m, SD_BUS_ERROR_INVALID_ARGS, "Invalid argument for the node name");
1175
        }
1176

1177
        node = controller_find_node(controller, node_name);
24✔
1178
        if (node == NULL) {
24✔
1179
                return sd_bus_reply_method_errorf(m, SD_BUS_ERROR_SERVICE_UNKNOWN, "Node not found");
×
1180
        }
1181

1182
        r = sd_bus_message_new_method_return(m, &reply);
24✔
1183
        if (r < 0) {
24✔
1184
                return sd_bus_reply_method_errorf(
×
1185
                                reply,
1186
                                SD_BUS_ERROR_FAILED,
1187
                                "Failed to create a reply message: %s",
1188
                                strerror(-r));
1189
        }
1190

1191
        r = sd_bus_message_append(reply, "o", node->object_path);
24✔
1192
        if (r < 0) {
24✔
1193
                return sd_bus_reply_method_errorf(
×
1194
                                reply,
1195
                                SD_BUS_ERROR_FAILED,
1196
                                "Failed to append the object path of the node to the reply message: %s",
1197
                                strerror(-r));
1198
        }
1199

1200
        return sd_bus_message_send(reply);
24✔
1201
}
1202

1203
/************************************************************************
1204
 ***** org.eclipse.bluechi.Controller.CreateMonitor **********
1205
 ************************************************************************/
1206

1207
static int controller_method_create_monitor(sd_bus_message *m, void *userdata, UNUSED sd_bus_error *ret_error) {
9✔
1208
        Controller *controller = userdata;
9✔
1209
        _cleanup_sd_bus_message_ sd_bus_message *reply = NULL;
9✔
1210

1211
        _cleanup_monitor_ Monitor *monitor = monitor_new(controller, sd_bus_message_get_sender(m));
18✔
1212
        if (monitor == NULL) {
9✔
1213
                return sd_bus_reply_method_errorf(reply, SD_BUS_ERROR_FAILED, "Failed to create new monitor");
×
1214
        }
1215

1216
        if (!monitor_export(monitor)) {
9✔
1217
                return sd_bus_reply_method_errorf(reply, SD_BUS_ERROR_FAILED, "Failed to export monitor");
×
1218
        }
1219

1220
        int r = sd_bus_message_new_method_return(m, &reply);
9✔
1221
        if (r < 0) {
9✔
1222
                return sd_bus_reply_method_errorf(
×
1223
                                reply,
1224
                                SD_BUS_ERROR_FAILED,
1225
                                "Failed to create a reply message for the monitor: %s",
1226
                                strerror(-r));
1227
        }
1228

1229
        r = sd_bus_message_append(reply, "o", monitor->object_path);
9✔
1230
        if (r < 0) {
9✔
1231
                return sd_bus_reply_method_errorf(
×
1232
                                reply,
1233
                                SD_BUS_ERROR_FAILED,
1234
                                "Failed to append the object path of the monitor to the reply message: %s",
1235
                                strerror(-r));
1236
        }
1237

1238
        r = sd_bus_message_send(reply);
9✔
1239
        if (r < 0) {
9✔
1240
                return r;
1241
        }
1242

1243
        /* We reported it to the client, now keep it alive and keep track of it */
1244
        LIST_APPEND(monitors, controller->monitors, monitor_ref(monitor));
9✔
1245
        return 1;
1246
}
1247

1248
/************************************************************************
1249
 ***** org.eclipse.bluechi.Controller.EnableMetrics **********
1250
 ************************************************************************/
1251
static int controller_method_metrics_enable(sd_bus_message *m, void *userdata, UNUSED sd_bus_error *ret_error) {
1✔
1252
        Controller *controller = userdata;
1✔
1253
        Node *node = NULL;
1✔
1254
        int r = 0;
1✔
1255
        if (controller->metrics_enabled) {
1✔
1256
                return sd_bus_reply_method_errorf(
×
1257
                                m, SD_BUS_ERROR_INCONSISTENT_MESSAGE, "Metrics already enabled");
1258
        }
1259
        r = metrics_export(controller);
1✔
1260
        if (r < 0) {
1✔
1261
                return sd_bus_reply_method_errorf(
×
1262
                                m, SD_BUS_ERROR_FAILED, "Failed to register metrics service: %s", strerror(-r));
1263
        }
1264
        controller->metrics_enabled = true;
1✔
1265
        LIST_FOREACH(nodes, node, controller->nodes) {
3✔
1266
                node_enable_metrics(node);
2✔
1267
        }
1268
        bc_log_debug("Metrics enabled");
1✔
1269
        return sd_bus_reply_method_return(m, "");
1✔
1270
}
1271

1272
/************************************************************************
1273
 ***** org.eclipse.bluechi.Controller.DisableMetrics *********
1274
 ************************************************************************/
1275
static int controller_method_metrics_disable(sd_bus_message *m, void *userdata, UNUSED sd_bus_error *ret_error) {
1✔
1276
        Controller *controller = userdata;
1✔
1277
        Node *node = NULL;
1✔
1278
        if (!controller->metrics_enabled) {
1✔
1279
                return sd_bus_reply_method_errorf(
×
1280
                                m, SD_BUS_ERROR_INCONSISTENT_MESSAGE, "Metrics already disabled");
1281
        }
1282
        sd_bus_slot_unrefp(&controller->metrics_slot);
1✔
1283
        controller->metrics_slot = NULL;
1✔
1284
        controller->metrics_enabled = false;
1✔
1285
        LIST_FOREACH(nodes, node, controller->nodes) {
3✔
1286
                node_disable_metrics(node);
2✔
1287
        }
1288
        bc_log_debug("Metrics disabled");
1✔
1289
        return sd_bus_reply_method_return(m, "");
1✔
1290
}
1291

1292
/*************************************************************************
1293
 *** org.eclipse.bluechi.Controller.SetLogLevel ***************
1294
 *************************************************************************/
1295

1296
static int controller_method_set_log_level(
×
1297
                sd_bus_message *m, UNUSED void *userdata, UNUSED sd_bus_error *ret_error) {
1298
        const char *level = NULL;
×
1299

1300
        int r = sd_bus_message_read(m, "s", &level);
×
1301
        if (r < 0) {
×
1302
                bc_log_errorf("Failed to read the parameter: %s", strerror(-r));
×
1303
                return sd_bus_reply_method_errorf(
×
1304
                                m, SD_BUS_ERROR_FAILED, "Failed to read the parameter: %s", strerror(-r));
1305
        }
1306
        LogLevel loglevel = string_to_log_level(level);
×
1307
        if (loglevel == LOG_LEVEL_INVALID) {
×
1308
                bc_log_errorf("Invalid input for log level: %s", level);
×
1309
                return sd_bus_reply_method_errorf(m, SD_BUS_ERROR_INVALID_ARGS, "Invalid input for log level");
×
1310
        }
1311
        bc_log_set_level(loglevel);
×
1312
        bc_log_infof("Log level changed to %s", level);
×
1313
        return sd_bus_reply_method_return(m, "");
×
1314
}
1315

1316
/********************************************************
1317
 **** org.eclipse.bluechi.Controller.Status ****************
1318
 ********************************************************/
1319

1320
static char *controller_get_system_status(Controller *controller) {
221✔
1321
        if (controller->number_of_nodes_online == 0) {
221✔
1322
                return "down";
1323
        } else if (controller->number_of_nodes_online == controller->number_of_nodes) {
163✔
1324
                return "up";
61✔
1325
        }
1326
        return "degraded";
1327
}
1328

1329
void controller_check_system_status(Controller *controller, int prev_number_of_nodes_online) {
236✔
1330
        int diff = controller->number_of_nodes_online - prev_number_of_nodes_online;
236✔
1331
        // clang-format off
1332
        if ((prev_number_of_nodes_online == 0) ||                                          // at least one node online
236✔
1333
                (prev_number_of_nodes_online == controller->number_of_nodes) ||            // at least one node offline
172✔
1334
                ((prev_number_of_nodes_online + diff) == controller->number_of_nodes) ||   // all nodes online
65✔
1335
                ((prev_number_of_nodes_online + diff) == 0)) {                             // all nodes offline
1336
                // clang-format on
1337
                int r = sd_bus_emit_properties_changed(
217✔
1338
                                controller->api_bus,
1339
                                BC_CONTROLLER_OBJECT_PATH,
1340
                                CONTROLLER_INTERFACE,
1341
                                "Status",
1342
                                NULL);
1343
                if (r < 0) {
217✔
1344
                        bc_log_errorf("Failed to emit status property changed: %s", strerror(-r));
×
1345
                }
1346
        }
1347
}
236✔
1348

1349
static int controller_property_get_status(
221✔
1350
                UNUSED sd_bus *bus,
1351
                UNUSED const char *path,
1352
                UNUSED const char *interface,
1353
                UNUSED const char *property,
1354
                sd_bus_message *reply,
1355
                void *userdata,
1356
                UNUSED sd_bus_error *ret_error) {
1357
        Controller *controller = userdata;
221✔
1358

1359
        return sd_bus_message_append(reply, "s", controller_get_system_status(controller));
221✔
1360
}
1361

1362
static int controller_property_get_loglevel(
×
1363
                UNUSED sd_bus *bus,
1364
                UNUSED const char *path,
1365
                UNUSED const char *interface,
1366
                UNUSED const char *property,
1367
                sd_bus_message *reply,
1368
                UNUSED void *userdata,
1369
                UNUSED sd_bus_error *ret_error) {
1370
        const char *log_level = log_level_to_string(bc_log_get_level());
×
1371
        return sd_bus_message_append(reply, "s", log_level);
×
1372
}
1373

1374
static int controller_property_get_log_target(
×
1375
                UNUSED sd_bus *bus,
1376
                UNUSED const char *path,
1377
                UNUSED const char *interface,
1378
                UNUSED const char *property,
1379
                sd_bus_message *reply,
1380
                UNUSED void *userdata,
1381
                UNUSED sd_bus_error *ret_error) {
1382
        return sd_bus_message_append(reply, "s", log_target_to_str(bc_log_get_log_fn()));
×
1383
}
1384

1385
static const sd_bus_vtable controller_vtable[] = {
1386
        SD_BUS_VTABLE_START(0),
1387
        SD_BUS_METHOD("ListUnits", "", NODE_AND_UNIT_INFO_DICT_ARRAY_TYPESTRING, controller_method_list_units, 0),
1388
        SD_BUS_METHOD("ListUnitFiles",
1389
                      "",
1390
                      NODE_AND_UNIT_FILE_INFO_DICT_ARRAY_TYPESTRING,
1391
                      controller_method_list_unit_files,
1392
                      0),
1393
        SD_BUS_METHOD("ListNodes", "", "a(soss)", controller_method_list_nodes, 0),
1394
        SD_BUS_METHOD("GetNode", "s", "o", controller_method_get_node, 0),
1395
        SD_BUS_METHOD("CreateMonitor", "", "o", controller_method_create_monitor, 0),
1396
        SD_BUS_METHOD("SetLogLevel", "s", "", controller_method_set_log_level, 0),
1397
        SD_BUS_METHOD("EnableMetrics", "", "", controller_method_metrics_enable, 0),
1398
        SD_BUS_METHOD("DisableMetrics", "", "", controller_method_metrics_disable, 0),
1399
        SD_BUS_SIGNAL_WITH_NAMES("JobNew", "uo", SD_BUS_PARAM(id) SD_BUS_PARAM(job), 0),
1400
        SD_BUS_SIGNAL_WITH_NAMES(
1401
                        "JobRemoved",
1402
                        "uosss",
1403
                        SD_BUS_PARAM(id) SD_BUS_PARAM(job) SD_BUS_PARAM(node) SD_BUS_PARAM(unit)
1404
                                        SD_BUS_PARAM(result),
1405
                        0),
1406
        SD_BUS_PROPERTY("LogLevel", "s", controller_property_get_loglevel, 0, SD_BUS_VTABLE_PROPERTY_EXPLICIT),
1407
        SD_BUS_PROPERTY("LogTarget", "s", controller_property_get_log_target, 0, SD_BUS_VTABLE_PROPERTY_CONST),
1408
        SD_BUS_PROPERTY("Status", "s", controller_property_get_status, 0, SD_BUS_VTABLE_PROPERTY_EMITS_CHANGE),
1409
        SD_BUS_VTABLE_END
1410
};
1411

1412
static int controller_dbus_filter(UNUSED sd_bus_message *m, void *userdata, UNUSED sd_bus_error *ret_error) {
756✔
1413
        Controller *controller = userdata;
756✔
1414
        const char *object_path = sd_bus_message_get_path(m);
756✔
1415
        const char *iface = sd_bus_message_get_interface(m);
756✔
1416

1417
        if (DEBUG_MESSAGES) {
756✔
1418
                bc_log_infof("Incoming public message: path: %s, iface: %s, member: %s, signature: '%s'",
1419
                             object_path,
1420
                             iface,
1421
                             sd_bus_message_get_member(m),
1422
                             sd_bus_message_get_signature(m, true));
1423
        }
1424

1425
        if (iface != NULL && streq(iface, NODE_INTERFACE)) {
756✔
1426
                Node *node = controller_find_node_by_path(controller, object_path);
57✔
1427

1428
                /* All Node interface objects fail if the node is offline */
1429
                if (node && !node_has_agent(node)) {
57✔
1430
                        return sd_bus_reply_method_errorf(m, BC_BUS_ERROR_OFFLINE, "Node is offline");
×
1431
                }
1432
        }
1433

1434
        return 0;
1435
}
1436

1437
void controller_remove_monitor(Controller *controller, Monitor *monitor) {
9✔
1438
        LIST_REMOVE(monitors, controller->monitors, monitor);
9✔
1439
        monitor_unref(monitor);
9✔
1440
}
9✔
1441

1442
static void controller_client_disconnected(Controller *controller, const char *client_id) {
145✔
1443
        /* Free any monitors owned by the client */
1444

1445
        Monitor *monitor = NULL;
145✔
1446
        Monitor *next_monitor = NULL;
145✔
1447
        LIST_FOREACH_SAFE(monitors, monitor, next_monitor, controller->monitors) {
158✔
1448
                if (streq(monitor->owner, client_id)) {
13✔
1449
                        monitor_close(monitor);
9✔
1450
                        controller_remove_monitor(controller, monitor);
9✔
1451
                }
1452
        }
1453
}
145✔
1454

1455
static int controller_name_owner_changed(sd_bus_message *m, void *userdata, UNUSED sd_bus_error *ret_error) {
408✔
1456
        Controller *controller = userdata;
408✔
1457
        const char *name = NULL;
408✔
1458
        const char *old_owner = NULL;
408✔
1459
        const char *new_owner = NULL;
408✔
1460

1461
        int r = sd_bus_message_read(m, "sss", &name, &old_owner, &new_owner);
408✔
1462
        if (r < 0) {
408✔
1463
                return r;
408✔
1464
        }
1465

1466
        if (*name == ':' && *new_owner == 0) {
408✔
1467
                controller_client_disconnected(controller, name);
145✔
1468
        }
1469

1470
        return 0;
1471
}
1472

1473
bool controller_start(Controller *controller) {
58✔
1474
        bc_log_infof("Starting bluechi-controller %s", CONFIG_H_BC_VERSION);
58✔
1475
        if (controller == NULL) {
58✔
1476
                return false;
58✔
1477
        }
1478

1479
#ifdef USE_USER_API_BUS
1480
        controller->api_bus = user_bus_open(controller->event);
1481
#else
1482
        controller->api_bus = system_bus_open(controller->event);
58✔
1483
#endif
1484
        if (controller->api_bus == NULL) {
58✔
1485
                bc_log_error("Failed to open api dbus");
×
1486
                return false;
×
1487
        }
1488

1489
        /* Export all known nodes */
1490
        Node *node = NULL;
58✔
1491
        LIST_FOREACH(nodes, node, controller->nodes) {
323✔
1492
                if (!node_export(node)) {
265✔
1493
                        return false;
1494
                }
1495
        }
1496

1497
        int r = sd_bus_request_name(
116✔
1498
                        controller->api_bus, controller->api_bus_service_name, SD_BUS_NAME_REPLACE_EXISTING);
58✔
1499
        if (r < 0) {
58✔
1500
                bc_log_errorf("Failed to acquire service name on api dbus: %s", strerror(-r));
×
1501
                return false;
×
1502
        }
1503

1504
        r = sd_bus_add_filter(
58✔
1505
                        controller->api_bus, &controller->filter_slot, controller_dbus_filter, controller);
1506
        if (r < 0) {
58✔
1507
                bc_log_errorf("Failed to add controller filter: %s", strerror(-r));
×
1508
                return false;
×
1509
        }
1510

1511
        r = sd_bus_match_signal(
58✔
1512
                        controller->api_bus,
1513
                        &controller->name_owner_changed_slot,
1514
                        "org.freedesktop.DBus",
1515
                        "/org/freedesktop/DBus",
1516
                        "org.freedesktop.DBus",
1517
                        "NameOwnerChanged",
1518
                        controller_name_owner_changed,
1519
                        controller);
1520
        if (r < 0) {
58✔
1521
                bc_log_errorf("Failed to add nameloist filter: %s", strerror(-r));
×
1522
                return false;
×
1523
        }
1524

1525
        r = sd_bus_add_object_vtable(
58✔
1526
                        controller->api_bus,
1527
                        &controller->controller_slot,
1528
                        BC_CONTROLLER_OBJECT_PATH,
1529
                        CONTROLLER_INTERFACE,
1530
                        controller_vtable,
1531
                        controller);
1532
        if (r < 0) {
58✔
1533
                bc_log_errorf("Failed to add controller vtable: %s", strerror(-r));
×
1534
                return false;
×
1535
        }
1536

1537
        if (!controller_setup_node_connection_handler(controller)) {
58✔
1538
                return false;
1539
        }
1540

1541
        r = controller_setup_heartbeat_timer(controller);
58✔
1542
        if (r < 0) {
58✔
1543
                bc_log_errorf("Failed to set up controller heartbeat timer: %s", strerror(-r));
×
1544
                return false;
×
1545
        }
1546

1547
        ShutdownHook hook;
58✔
1548
        hook.shutdown = (ShutdownHookFn) controller_stop;
58✔
1549
        hook.userdata = controller;
58✔
1550
        r = event_loop_add_shutdown_signals(controller->event, &hook);
58✔
1551
        if (r < 0) {
58✔
1552
                bc_log_errorf("Failed to add signals to controller event loop: %s", strerror(-r));
×
1553
                return false;
×
1554
        }
1555

1556
        r = sd_event_loop(controller->event);
58✔
1557
        if (r < 0) {
58✔
1558
                bc_log_errorf("Starting event loop failed: %s", strerror(-r));
×
1559
                return false;
×
1560
        }
1561

1562
        return true;
1563
}
1564

1565
void controller_stop(Controller *controller) {
58✔
1566
        if (controller == NULL) {
58✔
1567
                return;
1568
        }
1569

1570
        bc_log_debug("Stopping controller");
58✔
1571

1572
        Job *job = NULL;
58✔
1573
        Job *next_job = NULL;
58✔
1574
        LIST_FOREACH_SAFE(jobs, job, next_job, controller->jobs) {
58✔
1575
                controller_remove_job(controller, job, "cancelled due to shutdown");
×
1576
        }
1577

1578
        Subscription *sub = NULL;
58✔
1579
        Subscription *next_sub = NULL;
58✔
1580
        LIST_FOREACH_SAFE(all_subscriptions, sub, next_sub, controller->all_subscriptions) {
58✔
1581
                controller_remove_subscription(controller, sub);
×
1582
        }
1583

1584
        Monitor *monitor = NULL;
58✔
1585
        Monitor *next_monitor = NULL;
58✔
1586
        LIST_FOREACH_SAFE(monitors, monitor, next_monitor, controller->monitors) {
58✔
1587
                controller_remove_monitor(controller, monitor);
×
1588
        }
1589

1590
        /* If all nodes were already offline, we don't need to emit a changed signal */
1591
        bool status_changed = controller->number_of_nodes_online > 0;
58✔
1592

1593
        Node *node = NULL;
58✔
1594
        Node *next_node = NULL;
58✔
1595
        LIST_FOREACH_SAFE(nodes, node, next_node, controller->nodes) {
323✔
1596
                controller_remove_node(controller, node);
265✔
1597
        }
1598
        LIST_FOREACH_SAFE(nodes, node, next_node, controller->anonymous_nodes) {
179✔
1599
                controller_remove_node(controller, node);
121✔
1600
        }
1601

1602
        /*
1603
         * We won't handle any other events incl. node disconnected since we exit the event loop
1604
         * right afterwards. Therefore, check the controller state and emit signal here.
1605
         */
1606
        if (status_changed) {
58✔
1607
                controller_check_system_status(controller, controller->number_of_nodes_online);
6✔
1608
        }
1609
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2025 Coveralls, Inc