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

eclipse-bluechi / bluechi / 16142519133

08 Jul 2025 11:56AM UTC coverage: 74.418% (-8.0%) from 82.434%
16142519133

push

github

engelmi
Added epel10 target for copr_build via packit

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

5117 of 6876 relevant lines covered (74.42%)

870.33 hits per line

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

76.69
/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) {
60✔
28
        int r = 0;
60✔
29
        _cleanup_sd_event_ sd_event *event = NULL;
60✔
30
        r = sd_event_default(&event);
60✔
31
        if (r < 0) {
60✔
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);
60✔
37
        if (service_name == NULL) {
60✔
38
                bc_log_error("Out of memory");
×
39
                return NULL;
40
        }
41

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

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

67
        return controller;
68
}
69

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

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

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

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

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

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

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

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

105
                /* Remove UDS socket for proper cleanup and not cause an address in use error */
106
                unlink(CONFIG_H_UDS_SOCKET_PATH);
52✔
107
        }
108
        if (controller->node_connection_systemd_socket_source != NULL) {
60✔
109
                sd_event_source_unrefp(&controller->node_connection_systemd_socket_source);
2✔
110
                controller->node_connection_systemd_socket_source = NULL;
2✔
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);
2✔
116
        }
117

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

124
        free(controller);
60✔
125
}
126

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

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

132
        if (subscription_has_node_wildcard(sub)) {
11✔
133
                LIST_FOREACH(nodes, node, controller->nodes) {
×
134
                        node_subscribe(node, sub);
×
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) {
11✔
148
        Node *node = NULL;
11✔
149

150
        if (subscription_has_node_wildcard(sub)) {
11✔
151
                LIST_FOREACH(nodes, node, controller->nodes) {
×
152
                        node_unsubscribe(node, sub);
×
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);
11✔
162
        subscription_unref(sub);
11✔
163
}
11✔
164

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

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

174
        return NULL;
175
}
176

177

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

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

187
        return NULL;
188
}
189

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

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

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

210
        int r = sd_bus_emit_signal(
34✔
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) {
34✔
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));
34✔
224
        return true;
225
}
226

227
void controller_remove_job(Controller *controller, Job *job, const char *result) {
33✔
228
        int r = sd_bus_emit_signal(
66✔
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,
33✔
237
                        job->unit,
238
                        result);
239
        if (r < 0) {
33✔
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);
33✔
245
        if (controller->metrics_enabled && streq(job->type, "start")) {
33✔
246
                metrics_produce_job_report(job);
1✔
247
        }
248
        job_unref(job);
33✔
249
}
33✔
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) {
33✔
263
        Job *job = NULL;
33✔
264
        LIST_FOREACH(jobs, job, controller->jobs) {
33✔
265
                if (job->id == job_id) {
33✔
266
                        if (controller->metrics_enabled) {
33✔
267
                                job->job_end_micros = get_time_micros();
1✔
268
                        }
269
                        controller_remove_job(controller, job, result);
33✔
270
                        break;
33✔
271
                }
272
        }
273
}
33✔
274

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

281
        if (name) {
214✔
282
                controller->number_of_nodes++;
107✔
283
                LIST_APPEND(nodes, controller->nodes, node);
116✔
284
        } else {
285
                LIST_APPEND(nodes, controller->anonymous_nodes, node);
124✔
286
        }
287

288
        return steal_pointer(&node);
289
}
290

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

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

301

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

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

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

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

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

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

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

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

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

350
        result = cfg_load_complete_configuration(
54✔
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) {
54✔
357
                return false;
×
358
        }
359
        return true;
360
}
361

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

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

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

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

390
                char *name = strtok_r(expected_nodes_cpy, ",", &saveptr);
55✔
391
                while (name != NULL) {
167✔
392
                        if (controller_find_node(controller, name) == NULL) {
112✔
393
                                controller_add_node(controller, name);
107✔
394
                        }
395

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

400
        _cleanup_freev_ char **sections = cfg_list_sections(controller->config);
118✔
401
        if (sections == NULL) {
59✔
402
                bc_log_error("Failed to list config sections");
×
403
                return false;
404
        }
405
        for (size_t i = 0; sections[i] != NULL; i++) {
124✔
406
                const char *section = sections[i];
65✔
407
                if (!str_has_prefix(section, CFG_SECT_NODE_PREFIX)) {
65✔
408
                        continue;
58✔
409
                }
410
                const char *node_name = section + strlen(CFG_SECT_NODE_PREFIX);
7✔
411
                Node *node = controller_find_node(controller, node_name);
7✔
412
                if (node == NULL) {
7✔
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(
7✔
422
                                controller->config, section, CFG_REQUIRED_SELINUX_CONTEXT);
423
                if (selinux_context && !node_set_required_selinux_context(node, selinux_context)) {
7✔
424
                        return false;
425
                }
426

427
                const char *proxy_enabled_nodes = cfg_s_get_value(
7✔
428
                                controller->config, section, CFG_ALLOW_DEPENDENCIES_ON);
429
                if (proxy_enabled_nodes) {
7✔
430
                        char *saveptr = NULL;
7✔
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;
7✔
434
                        copy_str(&proxy_enabled_nodes_cpy, proxy_enabled_nodes);
7✔
435

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

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

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

454
        const char *threshold_msec = cfg_get_value(controller->config, CFG_NODE_HEARTBEAT_THRESHOLD);
59✔
455
        if (threshold_msec) {
59✔
456
                if (!controller_set_heartbeat_threshold(controller, threshold_msec)) {
54✔
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);
59✔
463
        if (keepidle) {
59✔
464
                if (socket_options_set_tcp_keepidle(controller->peer_socket_options, keepidle) < 0) {
56✔
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);
58✔
470
        if (keepintvl) {
58✔
471
                if (socket_options_set_tcp_keepintvl(controller->peer_socket_options, keepintvl) < 0) {
56✔
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);
57✔
477
        if (keepcnt) {
57✔
478
                if (socket_options_set_tcp_keepcnt(controller->peer_socket_options, keepcnt) < 0) {
56✔
479
                        bc_log_error("Failed to set TCP KEEPCNT");
1✔
480
                        return false;
481
                }
482
        }
483
        if (socket_options_set_ip_recverr(
56✔
484
                            controller->peer_socket_options,
485
                            cfg_get_bool_value(controller->config, CFG_IP_RECEIVE_ERRORS)) < 0) {
56✔
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(
107✔
494
                UNUSED sd_event_source *source, int fd, UNUSED uint32_t revents, void *userdata) {
495
        Controller *controller = userdata;
107✔
496
        Node *node = NULL;
107✔
497
        _cleanup_fd_ int nfd = accept_connection_request(fd);
107✔
498
        if (nfd < 0) {
107✔
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(
214✔
504
                        controller->event, "managed-node", BC_DBUS_NAME, steal_fd(&nfd));
505
        if (dbus_server == NULL) {
107✔
506
                return -1;
507
        }
508

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

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

517
        if (!node_set_agent_bus(node, dbus_server)) {
107✔
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) {
54✔
526
        int r = 0;
54✔
527
        _cleanup_sd_event_source_ sd_event_source *event_source = NULL;
54✔
528

529
        int n = sd_listen_fds(0);
54✔
530
        if (n < 1) {
54✔
531
                bc_log_debug("No socket unit file descriptor has been passed");
52✔
532
                return true;
533
        }
534
        if (n > 1) {
2✔
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(
2✔
540
                        controller->event,
541
                        &event_source,
542
                        SD_LISTEN_FDS_START,
543
                        EPOLLIN,
544
                        controller_accept_node_connection,
545
                        controller);
546
        if (r < 0) {
2✔
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);
2✔
551
        if (r < 0) {
2✔
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");
2✔
557
        controller->node_connection_systemd_socket_source = steal_pointer(&event_source);
2✔
558

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

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

568
        tcp_fd = create_tcp_socket(controller->port);
52✔
569
        if (tcp_fd < 0) {
52✔
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(
52✔
587
                        controller->event,
588
                        &event_source,
589
                        tcp_fd,
590
                        EPOLLIN,
591
                        controller_accept_node_connection,
592
                        controller);
593
        if (r < 0) {
52✔
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);
52✔
598
        if (r < 0) {
52✔
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);
52✔
604

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

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

612

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

618
        uds_fd = create_uds_socket(CONFIG_H_UDS_SOCKET_PATH);
54✔
619
        if (uds_fd < 0) {
54✔
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 &&
4✔
626
                    sd_is_socket_unix(SD_LISTEN_FDS_START, AF_UNIX, 1, CONFIG_H_UDS_SOCKET_PATH, 0) > 0) {
2✔
627
                        bc_log_warnf("UDS socket for path %s already setup with systemd socket unit",
2✔
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(
52✔
645
                        controller->event,
646
                        &event_source,
647
                        uds_fd,
648
                        EPOLLIN,
649
                        controller_accept_node_connection,
650
                        controller);
651
        if (r < 0) {
52✔
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);
52✔
656
        if (r < 0) {
52✔
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);
52✔
662

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

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

670

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

682
        if (controller->node_connection_systemd_socket_source == NULL &&
54✔
683
            controller->node_connection_tcp_socket_source == NULL &&
52✔
684
            controller->node_connection_uds_socket_source == NULL) {
2✔
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) {
6✔
694
        uint64_t diff = 0;
6✔
695

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

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

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

711
        diff = now - node->last_seen_monotonic;
6✔
712
        if (diff > (uint64_t) controller->heartbeat_threshold_msec * USEC_PER_MSEC) {
6✔
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(
3✔
724
                sd_event_source *event_source, UNUSED uint64_t usec, void *userdata) {
725
        Controller *controller = (Controller *) userdata;
3✔
726
        Node *node = NULL;
3✔
727
        uint64_t now = get_time_micros_monotonic();
3✔
728
        int r = 0;
3✔
729

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

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

739
                r = sd_bus_emit_signal(
5✔
740
                                node->agent_bus,
741
                                INTERNAL_CONTROLLER_OBJECT_PATH,
742
                                INTERNAL_CONTROLLER_INTERFACE,
743
                                "Heartbeat",
744
                                "");
745
                if (r < 0) {
5✔
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);
3✔
753
        if (r < 0) {
3✔
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) {
4✔
762
        return event_reset_time_relative(
8✔
763
                        controller->event,
764
                        event_source,
765
                        CLOCK_BOOTTIME,
766
                        controller->heartbeat_interval_msec * USEC_PER_MSEC,
4✔
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) {
54✔
776
        _cleanup_(sd_event_source_unrefp) sd_event_source *event_source = NULL;
54✔
777
        int r = 0;
54✔
778

779
        assert(controller);
54✔
780

781
        if (controller->heartbeat_interval_msec <= 0) {
54✔
782
                bc_log_warnf("Heartbeat disabled since configured interval '%d' is <=0",
53✔
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) {
1✔
820
        sd_bus_message_unref(req->request_message);
1✔
821

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

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

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

838
#define _cleanup_agent_fleet_request_ _cleanup_(agent_fleet_request_freep)
839

840
static void agent_fleet_request_done(AgentFleetRequest *req) {
1✔
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;
1✔
845
        int r = sd_bus_message_new_method_return(req->request_message, &reply);
1✔
846
        if (r < 0) {
1✔
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);
1✔
856
        if (r < 0) {
1✔
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);
1✔
866
        if (r < 0) {
1✔
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) {
4✔
873
        if (req->n_done == req->n_sub_req) {
4✔
874
                agent_fleet_request_done(req);
1✔
875
        }
876
}
4✔
877

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

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

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

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

894
        agent_fleet_request_maybe_done(req);
3✔
895

896
        return 0;
3✔
897
}
898

899
static int agent_fleet_request_start(
1✔
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;
1✔
905

906
        req = malloc0_array(sizeof(*req), sizeof(req->sub_req[0]), controller->number_of_nodes);
1✔
907
        if (req == NULL) {
1✔
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);
1✔
911
        req->encode = encode;
1✔
912

913
        Node *node = NULL;
1✔
914
        int i = 0;
1✔
915
        LIST_FOREACH(nodes, node, controller->nodes) {
4✔
916
                _cleanup_agent_request_ AgentRequest *agent_req = create_request(
6✔
917
                                node, agent_fleet_request_callback, req, NULL);
918
                if (agent_req) {
3✔
919
                        req->sub_req[i].agent_req = steal_pointer(&agent_req);
3✔
920
                        req->sub_req[i].node = node_ref(node);
3✔
921
                        req->n_sub_req++;
3✔
922
                        i++;
3✔
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);
1✔
934

935
        return 1;
1✔
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) {
×
944
        int r = sd_bus_message_open_container(reply, SD_BUS_TYPE_ARRAY, NODE_AND_UNIT_INFO_DICT_TYPESTRING);
×
945
        if (r < 0) {
×
946
                return r;
947
        }
948

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

956
                const sd_bus_error *err = sd_bus_message_get_error(m);
×
957
                if (err != NULL) {
×
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);
×
962
                if (r < 0) {
×
963
                        return r;
964
                }
965

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

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

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

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

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

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

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

1004
        r = sd_bus_message_close_container(reply);
×
1005
        if (r < 0) {
×
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) {
×
1013
        Controller *controller = userdata;
×
1014
        return agent_fleet_request_start(
×
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) {
12✔
1108
        int r = sd_bus_message_open_container(reply, SD_BUS_TYPE_STRUCT, "soss");
12✔
1109
        if (r < 0) {
12✔
1110
                return r;
1111
        }
1112

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

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

1126
        int r = sd_bus_message_new_method_return(m, &reply);
7✔
1127
        if (r < 0) {
7✔
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)");
7✔
1136
        if (r < 0) {
7✔
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) {
19✔
1145
                r = controller_method_list_encode_node(reply, node);
12✔
1146
                if (r < 0) {
12✔
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);
7✔
1153
        if (r < 0) {
7✔
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);
7✔
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) {
19✔
1166
        Controller *controller = userdata;
19✔
1167
        _cleanup_sd_bus_message_ sd_bus_message *reply = NULL;
19✔
1168
        Node *node = NULL;
19✔
1169
        const char *node_name = NULL;
19✔
1170

1171
        int r = sd_bus_message_read(m, "s", &node_name);
19✔
1172
        if (r < 0) {
19✔
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);
19✔
1178
        if (node == NULL) {
19✔
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);
19✔
1183
        if (r < 0) {
19✔
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);
19✔
1192
        if (r < 0) {
19✔
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);
19✔
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) {
4✔
1208
        Controller *controller = userdata;
4✔
1209
        _cleanup_sd_bus_message_ sd_bus_message *reply = NULL;
4✔
1210

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

1216
        if (!monitor_export(monitor)) {
4✔
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);
4✔
1221
        if (r < 0) {
4✔
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);
4✔
1230
        if (r < 0) {
4✔
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);
4✔
1239
        if (r < 0) {
4✔
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));
4✔
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) {
×
1276
        Controller *controller = userdata;
×
1277
        Node *node = NULL;
×
1278
        if (!controller->metrics_enabled) {
×
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);
×
1283
        controller->metrics_slot = NULL;
×
1284
        controller->metrics_enabled = false;
×
1285
        LIST_FOREACH(nodes, node, controller->nodes) {
×
1286
                node_disable_metrics(node);
×
1287
        }
1288
        bc_log_debug("Metrics disabled");
×
1289
        return sd_bus_reply_method_return(m, "");
×
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) {
203✔
1321
        if (controller->number_of_nodes_online == 0) {
203✔
1322
                return "down";
1323
        } else if (controller->number_of_nodes_online == controller->number_of_nodes) {
147✔
1324
                return "up";
59✔
1325
        }
1326
        return "degraded";
1327
}
1328

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

1349
static int controller_property_get_status(
203✔
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;
203✔
1358

1359
        return sd_bus_message_append(reply, "s", controller_get_system_status(controller));
203✔
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) {
682✔
1413
        Controller *controller = userdata;
682✔
1414
        const char *object_path = sd_bus_message_get_path(m);
682✔
1415
        const char *iface = sd_bus_message_get_interface(m);
682✔
1416

1417
        if (DEBUG_MESSAGES) {
682✔
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)) {
682✔
1426
                Node *node = controller_find_node_by_path(controller, object_path);
64✔
1427

1428
                /* All Node interface objects fail if the node is offline */
1429
                if (node && !node_has_agent(node)) {
64✔
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) {
4✔
1438
        LIST_REMOVE(monitors, controller->monitors, monitor);
4✔
1439
        monitor_unref(monitor);
4✔
1440
}
4✔
1441

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

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

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

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

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

1470
        return 0;
1471
}
1472

1473
bool controller_start(Controller *controller) {
54✔
1474
        bc_log_infof("Starting bluechi-controller %s", CONFIG_H_BC_VERSION);
54✔
1475
        if (controller == NULL) {
54✔
1476
                return false;
54✔
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);
54✔
1483
#endif
1484
        if (controller->api_bus == NULL) {
54✔
1485
                bc_log_error("Failed to open api dbus");
×
1486
                return false;
×
1487
        }
1488

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

1497
        int r = sd_bus_request_name(
108✔
1498
                        controller->api_bus, controller->api_bus_service_name, SD_BUS_NAME_REPLACE_EXISTING);
54✔
1499
        if (r < 0) {
54✔
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(
54✔
1505
                        controller->api_bus, &controller->filter_slot, controller_dbus_filter, controller);
1506
        if (r < 0) {
54✔
1507
                bc_log_errorf("Failed to add controller filter: %s", strerror(-r));
×
1508
                return false;
×
1509
        }
1510

1511
        r = sd_bus_match_signal(
54✔
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) {
54✔
1521
                bc_log_errorf("Failed to add nameloist filter: %s", strerror(-r));
×
1522
                return false;
×
1523
        }
1524

1525
        r = sd_bus_add_object_vtable(
54✔
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) {
54✔
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)) {
54✔
1538
                return false;
1539
        }
1540

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

1547
        ShutdownHook hook;
54✔
1548
        hook.shutdown = (ShutdownHookFn) controller_stop;
54✔
1549
        hook.userdata = controller;
54✔
1550
        r = event_loop_add_shutdown_signals(controller->event, &hook);
54✔
1551
        if (r < 0) {
54✔
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);
54✔
1557
        if (r < 0) {
54✔
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) {
54✔
1566
        if (controller == NULL) {
54✔
1567
                return;
1568
        }
1569

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

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

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

1584
        Monitor *monitor = NULL;
54✔
1585
        Monitor *next_monitor = NULL;
54✔
1586
        LIST_FOREACH_SAFE(monitors, monitor, next_monitor, controller->monitors) {
54✔
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;
54✔
1592

1593
        Node *node = NULL;
54✔
1594
        Node *next_node = NULL;
54✔
1595
        LIST_FOREACH_SAFE(nodes, node, next_node, controller->nodes) {
158✔
1596
                controller_remove_node(controller, node);
104✔
1597
        }
1598
        LIST_FOREACH_SAFE(nodes, node, next_node, controller->anonymous_nodes) {
161✔
1599
                controller_remove_node(controller, node);
107✔
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) {
54✔
1607
                controller_check_system_status(controller, controller->number_of_nodes_online);
8✔
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