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

eclipse-bluechi / bluechi / 12770717745

14 Jan 2025 03:17PM UTC coverage: 82.426% (+0.07%) from 82.356%
12770717745

push

github

alexlarsson
Config: Dump section headers in cfg_dump()

Now that we use more than one section, make the debug output
make sense by printing the section headers.

16 of 20 new or added lines in 1 file covered. (80.0%)

262 existing lines in 3 files now uncovered.

5600 of 6794 relevant lines covered (82.43%)

1113.47 hits per line

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

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

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

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

67
        return controller;
68
}
69

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

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

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

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

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

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

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

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

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

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

124
        free(controller);
122✔
125
}
126

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

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

132
        if (subscription_has_node_wildcard(sub)) {
26✔
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);
24✔
140
        if (node) {
24✔
141
                node_subscribe(node, sub);
23✔
142
        } else {
143
                bc_log_errorf("Warning: Subscription to non-existing node %s", sub->node);
1✔
144
        }
145
}
146

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

150
        if (subscription_has_node_wildcard(sub)) {
26✔
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);
24✔
156
                if (node) {
24✔
157
                        node_unsubscribe(node, sub);
23✔
158
                }
159
        }
160

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

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

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

174
        return NULL;
175
}
176

177

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

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

187
        return NULL;
188
}
189

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

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

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

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

227
void controller_remove_job(Controller *controller, Job *job, const char *result) {
64✔
228
        int r = sd_bus_emit_signal(
128✔
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,
64✔
237
                        job->unit,
238
                        result);
239
        if (r < 0) {
64✔
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);
64✔
245
        if (controller->metrics_enabled && streq(job->type, "start")) {
64✔
246
                metrics_produce_job_report(job);
3✔
247
        }
248
        job_unref(job);
64✔
249
}
64✔
250

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

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

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

281
        if (name) {
419✔
282
                controller->number_of_nodes++;
282✔
283
                LIST_APPEND(nodes, controller->nodes, node);
11,320✔
284
        } else {
285
                LIST_APPEND(nodes, controller->anonymous_nodes, node);
165✔
286
        }
287

288
        return steal_pointer(&node);
289
}
290

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

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

301

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

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

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

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

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

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

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

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

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

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

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

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

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

386
                /* copy string of expected nodes since */
387
                _cleanup_free_ char *expected_nodes_cpy = NULL;
97✔
388
                copy_str(&expected_nodes_cpy, expected_nodes);
97✔
389

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

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

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

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

428
        const char *interval_msec = cfg_get_value(controller->config, CFG_HEARTBEAT_INTERVAL);
108✔
429
        if (interval_msec) {
108✔
430
                if (!controller_set_heartbeat_interval(controller, interval_msec)) {
108✔
431
                        return false;
432
                }
433
        }
434

435
        const char *threshold_msec = cfg_get_value(controller->config, CFG_NODE_HEARTBEAT_THRESHOLD);
108✔
436
        if (threshold_msec) {
108✔
437
                if (!controller_set_heartbeat_threshold(controller, threshold_msec)) {
108✔
438
                        return false;
439
                }
440
        }
441

442
        /* Set socket options used for peer connections with the agents */
443
        const char *keepidle = cfg_get_value(controller->config, CFG_TCP_KEEPALIVE_TIME);
108✔
444
        if (keepidle) {
108✔
445
                if (socket_options_set_tcp_keepidle(controller->peer_socket_options, keepidle) < 0) {
108✔
UNCOV
446
                        bc_log_error("Failed to set TCP KEEPIDLE");
×
447
                        return false;
448
                }
449
        }
450
        const char *keepintvl = cfg_get_value(controller->config, CFG_TCP_KEEPALIVE_INTERVAL);
108✔
451
        if (keepintvl) {
108✔
452
                if (socket_options_set_tcp_keepintvl(controller->peer_socket_options, keepintvl) < 0) {
108✔
453
                        bc_log_error("Failed to set TCP KEEPINTVL");
×
454
                        return false;
455
                }
456
        }
457
        const char *keepcnt = cfg_get_value(controller->config, CFG_TCP_KEEPALIVE_COUNT);
108✔
458
        if (keepcnt) {
108✔
459
                if (socket_options_set_tcp_keepcnt(controller->peer_socket_options, keepcnt) < 0) {
108✔
UNCOV
460
                        bc_log_error("Failed to set TCP KEEPCNT");
×
461
                        return false;
462
                }
463
        }
464
        if (socket_options_set_ip_recverr(
108✔
465
                            controller->peer_socket_options,
466
                            cfg_get_bool_value(controller->config, CFG_IP_RECEIVE_ERRORS)) < 0) {
108✔
UNCOV
467
                bc_log_error("Failed to set IP RECVERR");
×
468
                return false;
469
        }
470

471
        return true;
472
}
473

474
static int controller_accept_node_connection(
137✔
475
                UNUSED sd_event_source *source, int fd, UNUSED uint32_t revents, void *userdata) {
476
        Controller *controller = userdata;
137✔
477
        Node *node = NULL;
137✔
478
        _cleanup_fd_ int nfd = accept_connection_request(fd);
137✔
479
        if (nfd < 0) {
137✔
UNCOV
480
                bc_log_errorf("Failed to accept connection request: %s", strerror(-nfd));
×
UNCOV
481
                return -1;
×
482
        }
483

484
        _cleanup_sd_bus_ sd_bus *dbus_server = peer_bus_open_server(
274✔
485
                        controller->event, "managed-node", BC_DBUS_NAME, steal_fd(&nfd));
486
        if (dbus_server == NULL) {
137✔
487
                return -1;
488
        }
489

490
        bus_socket_set_options(dbus_server, controller->peer_socket_options);
137✔
491

492
        /* Add anonymous node */
493
        node = controller_add_node(controller, NULL);
137✔
494
        if (node == NULL) {
137✔
495
                return -1;
496
        }
497

498
        if (!node_set_agent_bus(node, dbus_server)) {
137✔
UNCOV
499
                controller_remove_node(controller, steal_pointer(&node));
×
500
                return -1;
501
        }
502

503
        return 0;
504
}
505

506
static bool controller_setup_systemd_socket_connection_handler(Controller *controller) {
108✔
507
        int r = 0;
108✔
508
        _cleanup_sd_event_source_ sd_event_source *event_source = NULL;
108✔
509

510
        int n = sd_listen_fds(0);
108✔
511
        if (n < 1) {
108✔
512
                bc_log_debug("No socket unit file descriptor has been passed");
105✔
513
                return true;
514
        }
515
        if (n > 1) {
3✔
UNCOV
516
                bc_log_errorf("Received too many file descriptors from socket unit - %d", n);
×
517
                return false;
518
        }
519

520
        r = sd_event_add_io(
3✔
521
                        controller->event,
522
                        &event_source,
523
                        SD_LISTEN_FDS_START,
524
                        EPOLLIN,
525
                        controller_accept_node_connection,
526
                        controller);
527
        if (r < 0) {
3✔
528
                bc_log_errorf("Failed to add io event source for systemd socket unit: %s", strerror(-r));
×
529
                return false;
530
        }
531
        r = sd_event_source_set_io_fd_own(event_source, true);
3✔
532
        if (r < 0) {
3✔
UNCOV
533
                bc_log_errorf("Failed to set io fd own for systemd socket unit: %s", strerror(-r));
×
534
                return false;
535
        }
536

537
        (void) sd_event_source_set_description(event_source, "node-accept-systemd-socket");
3✔
538
        controller->node_connection_systemd_socket_source = steal_pointer(&event_source);
3✔
539

540
        bc_log_info("Waiting for connection requests on configured socket unit...");
3✔
541
        return true;
542
}
543

544
static bool controller_setup_tcp_connection_handler(Controller *controller) {
106✔
545
        int r = 0;
106✔
546
        _cleanup_fd_ int tcp_fd = -1;
106✔
547
        _cleanup_sd_event_source_ sd_event_source *event_source = NULL;
106✔
548

549
        tcp_fd = create_tcp_socket(controller->port);
106✔
550
        if (tcp_fd < 0) {
106✔
551
                /*
552
                 * Check if the address is already in use and if the systemd file descriptor already uses it.
553
                 * In case both conditions are true, only log a warning and proceed as successful since a
554
                 * proper TCP socket incl. handler has already been set up.
555
                 */
UNCOV
556
                if (tcp_fd == -EADDRINUSE && controller->node_connection_systemd_socket_source != NULL &&
×
UNCOV
557
                    sd_is_socket_inet(SD_LISTEN_FDS_START, AF_UNSPEC, SOCK_STREAM, 1, controller->port) > 0) {
×
UNCOV
558
                        bc_log_warnf("TCP socket for port %d already setup with systemd socket unit",
×
559
                                     controller->port);
560
                        return true;
561
                }
562

UNCOV
563
                bc_log_errorf("Failed to create TCP socket: %s", strerror(-tcp_fd));
×
564
                return false;
565
        }
566

567
        r = sd_event_add_io(
106✔
568
                        controller->event,
569
                        &event_source,
570
                        tcp_fd,
571
                        EPOLLIN,
572
                        controller_accept_node_connection,
573
                        controller);
574
        if (r < 0) {
106✔
UNCOV
575
                bc_log_errorf("Failed to add io event for tcp socket: %s", strerror(-r));
×
576
                return false;
577
        }
578
        r = sd_event_source_set_io_fd_own(event_source, true);
106✔
579
        if (r < 0) {
106✔
UNCOV
580
                bc_log_errorf("Failed to set io fd own for tcp socket: %s", strerror(-r));
×
581
                return false;
582
        }
583
        // sd_event_set_io_fd_own takes care of closing tcp_fd
584
        steal_fd(&tcp_fd);
106✔
585

586
        (void) sd_event_source_set_description(event_source, "node-accept-tcp-socket");
106✔
587
        controller->node_connection_tcp_socket_source = steal_pointer(&event_source);
106✔
588

589
        bc_log_infof("Waiting for connection requests on port %d...", controller->port);
106✔
590
        return true;
591
}
592

593

594
static bool controller_setup_uds_connection_handler(Controller *controller) {
108✔
595
        int r = 0;
108✔
596
        _cleanup_fd_ int uds_fd = -1;
108✔
597
        _cleanup_sd_event_source_ sd_event_source *event_source = NULL;
108✔
598

599
        uds_fd = create_uds_socket(CONFIG_H_UDS_SOCKET_PATH);
108✔
600
        if (uds_fd < 0) {
108✔
601
                /*
602
                 * Check if the uds path is already in use and if the systemd file descriptor already uses
603
                 * it. In case both conditions are true, only log a warning and proceed as successful since a
604
                 * proper UDS incl. handler has already been set up.
605
                 */
606
                if (uds_fd == -EADDRINUSE && controller->node_connection_systemd_socket_source != NULL &&
6✔
607
                    sd_is_socket_unix(SD_LISTEN_FDS_START, AF_UNIX, 1, CONFIG_H_UDS_SOCKET_PATH, 0) > 0) {
3✔
608
                        bc_log_warnf("UDS socket for path %s already setup with systemd socket unit",
3✔
609
                                     CONFIG_H_UDS_SOCKET_PATH);
610
                        return true;
UNCOV
611
                } else if (uds_fd == -EADDRINUSE) {
×
612
                        /* If address is in use, remove socket file and retry again */
UNCOV
613
                        unlink(CONFIG_H_UDS_SOCKET_PATH);
×
UNCOV
614
                        uds_fd = create_uds_socket(CONFIG_H_UDS_SOCKET_PATH);
×
UNCOV
615
                        if (uds_fd < 0) {
×
UNCOV
616
                                bc_log_errorf("Failed to create UDS socket: %s", strerror(-uds_fd));
×
617
                                return false;
618
                        }
619
                } else {
UNCOV
620
                        bc_log_errorf("Failed to create UDS socket: %s", strerror(-uds_fd));
×
621
                        return false;
622
                }
623
        }
624

625
        r = sd_event_add_io(
105✔
626
                        controller->event,
627
                        &event_source,
628
                        uds_fd,
629
                        EPOLLIN,
630
                        controller_accept_node_connection,
631
                        controller);
632
        if (r < 0) {
105✔
UNCOV
633
                bc_log_errorf("Failed to add io event for uds socket: %s", strerror(-r));
×
634
                return false;
635
        }
636
        r = sd_event_source_set_io_fd_own(event_source, true);
105✔
637
        if (r < 0) {
105✔
638
                bc_log_errorf("Failed to set io fd own for uds socket: %s", strerror(-r));
×
639
                return false;
640
        }
641
        // sd_event_set_io_fd_own takes care of closing uds_fd
642
        steal_fd(&uds_fd);
105✔
643

644
        (void) sd_event_source_set_description(event_source, "node-accept-uds-socket");
105✔
645
        controller->node_connection_uds_socket_source = steal_pointer(&event_source);
105✔
646

647
        bc_log_infof("Waiting for connection requests on socket %s...", CONFIG_H_UDS_SOCKET_PATH);
105✔
648
        return true;
649
}
650

651

652
static bool controller_setup_node_connection_handler(Controller *controller) {
108✔
653
        if (!controller_setup_systemd_socket_connection_handler(controller)) {
108✔
654
                return false;
655
        }
656
        if (controller->use_tcp && !controller_setup_tcp_connection_handler(controller)) {
108✔
657
                return false;
658
        }
659
        if (controller->use_uds && !controller_setup_uds_connection_handler(controller)) {
108✔
660
                return false;
661
        }
662

663
        if (controller->node_connection_systemd_socket_source == NULL &&
108✔
664
            controller->node_connection_tcp_socket_source == NULL &&
105✔
665
            controller->node_connection_uds_socket_source == NULL) {
2✔
UNCOV
666
                bc_log_error("No connection request handler configured");
×
UNCOV
667
                return false;
×
668
        }
669
        return true;
670
}
671

672
static int controller_reset_heartbeat_timer(Controller *controller, sd_event_source **event_source);
673

674
static bool controller_check_node_liveness(Controller *controller, Node *node, uint64_t now) {
4✔
675
        uint64_t diff = 0;
4✔
676

677
        if (controller->heartbeat_threshold_msec <= 0) {
4✔
678
                /* checking liveness of node by heartbeat disabled since configured threshold is <=0" */
679
                return true;
680
        }
681

682
        if (now == 0) {
4✔
UNCOV
683
                bc_log_error("Current time is wrong");
×
UNCOV
684
                return true;
×
685
        }
686

687
        if (now < node->last_seen_monotonic) {
4✔
UNCOV
688
                bc_log_error("Clock skew detected");
×
UNCOV
689
                return true;
×
690
        }
691

692
        diff = now - node->last_seen_monotonic;
4✔
693
        if (diff > (uint64_t) controller->heartbeat_threshold_msec * USEC_PER_MSEC) {
4✔
694
                bc_log_infof("Did not receive heartbeat from node '%s' since '%d'ms. Disconnecting it...",
1✔
695
                             node->name,
696
                             controller->heartbeat_threshold_msec);
697
                node_disconnect(node);
1✔
698
                return false;
1✔
699
        }
700

701
        return true;
702
}
703

704
static int controller_heartbeat_timer_callback(
4✔
705
                sd_event_source *event_source, UNUSED uint64_t usec, void *userdata) {
706
        Controller *controller = (Controller *) userdata;
4✔
707
        Node *node = NULL;
4✔
708
        uint64_t now = get_time_micros_monotonic();
4✔
709
        int r = 0;
4✔
710

711
        LIST_FOREACH(nodes, node, controller->nodes) {
8✔
712
                if (!node_is_online(node)) {
4✔
UNCOV
713
                        continue;
×
714
                }
715

716
                if (!controller_check_node_liveness(controller, node, now)) {
4✔
717
                        continue;
1✔
718
                }
719

720
                r = sd_bus_emit_signal(
3✔
721
                                node->agent_bus,
722
                                INTERNAL_CONTROLLER_OBJECT_PATH,
723
                                INTERNAL_CONTROLLER_INTERFACE,
724
                                "Heartbeat",
725
                                "");
726
                if (r < 0) {
3✔
UNCOV
727
                        bc_log_errorf("Failed to emit heartbeat signal to node '%s': %s",
×
728
                                      node->name,
729
                                      strerror(-r));
730
                }
731
        }
732

733
        r = controller_reset_heartbeat_timer(controller, &event_source);
4✔
734
        if (r < 0) {
4✔
UNCOV
735
                bc_log_errorf("Failed to reset controller heartbeat timer: %s", strerror(-r));
×
UNCOV
736
                return r;
×
737
        }
738

739
        return 0;
740
}
741

742
static int controller_reset_heartbeat_timer(Controller *controller, sd_event_source **event_source) {
5✔
743
        return event_reset_time_relative(
10✔
744
                        controller->event,
745
                        event_source,
746
                        CLOCK_BOOTTIME,
747
                        controller->heartbeat_interval_msec * USEC_PER_MSEC,
5✔
748
                        0,
749
                        controller_heartbeat_timer_callback,
750
                        controller,
751
                        0,
752
                        "controller-heartbeat-timer-source",
753
                        false);
754
}
755

756
static int controller_setup_heartbeat_timer(Controller *controller) {
108✔
757
        _cleanup_(sd_event_source_unrefp) sd_event_source *event_source = NULL;
108✔
758
        int r = 0;
108✔
759

760
        assert(controller);
108✔
761

762
        if (controller->heartbeat_interval_msec <= 0) {
108✔
763
                bc_log_warnf("Heartbeat disabled since configured interval '%d' is <=0",
107✔
764
                             controller->heartbeat_interval_msec);
765
                return 0;
766
        }
767

768
        r = controller_reset_heartbeat_timer(controller, &event_source);
1✔
769
        if (r < 0) {
1✔
UNCOV
770
                bc_log_errorf("Failed to reset controller heartbeat timer: %s", strerror(-r));
×
771
                return r;
772
        }
773

774
        return sd_event_source_set_floating(event_source, true);
1✔
775
}
776

777
/************************************************************************
778
 ***************** AgentFleetRequest ************************************
779
 ************************************************************************/
780

781
typedef struct AgentFleetRequest AgentFleetRequest;
782

783
typedef int (*agent_fleet_request_encode_reply_t)(AgentFleetRequest *req, sd_bus_message *reply);
784
typedef AgentRequest *(*agent_fleet_request_create_t)(
785
                Node *node, agent_request_response_t cb, void *userdata, free_func_t free_userdata);
786

787
typedef struct AgentFleetRequest {
788
        sd_bus_message *request_message;
789
        agent_fleet_request_encode_reply_t encode;
790

791
        int n_done;
792
        int n_sub_req;
793
        struct {
794
                Node *node;
795
                sd_bus_message *m;
796
                AgentRequest *agent_req;
797
        } sub_req[0];
798
} AgentFleetRequest;
799

800
static void agent_fleet_request_free(AgentFleetRequest *req) {
2✔
801
        sd_bus_message_unref(req->request_message);
2✔
802

803
        for (int i = 0; i < req->n_sub_req; i++) {
6✔
804
                node_unrefp(&req->sub_req[i].node);
4✔
805
                sd_bus_message_unrefp(&req->sub_req[i].m);
4✔
806
                agent_request_unrefp(&req->sub_req[i].agent_req);
4✔
807
        }
808

809
        free(req);
2✔
810
}
2✔
811

812
static void agent_fleet_request_freep(AgentFleetRequest **reqp) {
2✔
813
        if (reqp && *reqp) {
2✔
814
                agent_fleet_request_free(*reqp);
2✔
815
                *reqp = NULL;
2✔
816
        }
817
}
2✔
818

819
#define _cleanup_agent_fleet_request_ _cleanup_(agent_fleet_request_freep)
820

821
static void agent_fleet_request_done(AgentFleetRequest *req) {
2✔
822
        /* All sub_req-requests are done, collect results and free when done */
UNCOV
823
        UNUSED _cleanup_agent_fleet_request_ AgentFleetRequest *free_me = req;
×
824

825
        _cleanup_sd_bus_message_ sd_bus_message *reply = NULL;
2✔
826
        int r = sd_bus_message_new_method_return(req->request_message, &reply);
2✔
827
        if (r < 0) {
2✔
UNCOV
828
                sd_bus_reply_method_errorf(
×
829
                                req->request_message,
830
                                SD_BUS_ERROR_FAILED,
831
                                "Failed to create a reply message: %s",
832
                                strerror(-r));
833
                return;
834
        }
835

836
        r = req->encode(req, reply);
2✔
837
        if (r < 0) {
2✔
UNCOV
838
                sd_bus_reply_method_errorf(
×
839
                                req->request_message,
840
                                SD_BUS_ERROR_FAILED,
841
                                "Request to at least one node failed: %s",
842
                                strerror(-r));
843
                return;
844
        }
845

846
        r = sd_bus_message_send(reply);
2✔
847
        if (r < 0) {
2✔
UNCOV
848
                bc_log_errorf("Failed to send reply message: %s", strerror(-r));
×
849
                return;
850
        }
851
}
852

853
static void agent_fleet_request_maybe_done(AgentFleetRequest *req) {
6✔
854
        if (req->n_done == req->n_sub_req) {
6✔
855
                agent_fleet_request_done(req);
2✔
856
        }
857
}
6✔
858

859
static int agent_fleet_request_callback(
4✔
860
                AgentRequest *agent_req, sd_bus_message *m, UNUSED sd_bus_error *ret_error) {
861
        AgentFleetRequest *req = agent_req->userdata;
4✔
862
        int i = 0;
4✔
863

864
        for (i = 0; i < req->n_sub_req; i++) {
6✔
865
                if (req->sub_req[i].agent_req == agent_req) {
6✔
866
                        break;
867
                }
868
        }
869

870
        assert(i != req->n_sub_req); /* we should have found the sub_req request */
4✔
871

872
        req->sub_req[i].m = sd_bus_message_ref(m);
4✔
873
        req->n_done++;
4✔
874

875
        agent_fleet_request_maybe_done(req);
4✔
876

877
        return 0;
4✔
878
}
879

880
static int agent_fleet_request_start(
2✔
881
                sd_bus_message *request_message,
882
                Controller *controller,
883
                agent_fleet_request_create_t create_request,
884
                agent_fleet_request_encode_reply_t encode) {
885
        AgentFleetRequest *req = NULL;
2✔
886

887
        req = malloc0_array(sizeof(*req), sizeof(req->sub_req[0]), controller->number_of_nodes);
2✔
888
        if (req == NULL) {
2✔
UNCOV
889
                return sd_bus_reply_method_errorf(request_message, SD_BUS_ERROR_NO_MEMORY, "Out of memory");
×
890
        }
891
        req->request_message = sd_bus_message_ref(request_message);
2✔
892
        req->encode = encode;
2✔
893

894
        Node *node = NULL;
2✔
895
        int i = 0;
2✔
896
        LIST_FOREACH(nodes, node, controller->nodes) {
6✔
897
                _cleanup_agent_request_ AgentRequest *agent_req = create_request(
8✔
898
                                node, agent_fleet_request_callback, req, NULL);
899
                if (agent_req) {
4✔
900
                        req->sub_req[i].agent_req = steal_pointer(&agent_req);
4✔
901
                        req->sub_req[i].node = node_ref(node);
4✔
902
                        req->n_sub_req++;
4✔
903
                        i++;
4✔
904
                }
905
        }
906

907
// Disabling -Wanalyzer-malloc-leak temporarily due to false-positive
908
//      Leak detected is based on the assumption that controller_method_list_units_maybe_done is only
909
//      called once directly after iterating over the list - when the conditional to free req is false.
910
//      However, it does not take into account that controller_list_units_callback calls it for each node.
911
#pragma GCC diagnostic push
912
#pragma GCC diagnostic ignored "-Wanalyzer-malloc-leak"
913

914
        agent_fleet_request_maybe_done(req);
2✔
915

916
        return 1;
2✔
917
}
918
#pragma GCC diagnostic pop
919

920
/************************************************************************
921
 ************** org.eclipse.bluechi.Controller.ListUnits *****
922
 ************************************************************************/
923

924
static int controller_method_list_units_encode_reply(AgentFleetRequest *req, sd_bus_message *reply) {
1✔
925
        int r = sd_bus_message_open_container(reply, SD_BUS_TYPE_ARRAY, NODE_AND_UNIT_INFO_STRUCT_TYPESTRING);
1✔
926
        if (r < 0) {
1✔
927
                return r;
928
        }
929

930
        for (int i = 0; i < req->n_sub_req; i++) {
3✔
931
                const char *node_name = req->sub_req[i].node->name;
2✔
932
                sd_bus_message *m = req->sub_req[i].m;
2✔
933
                if (m == NULL) {
2✔
UNCOV
934
                        continue;
×
935
                }
936

937
                const sd_bus_error *err = sd_bus_message_get_error(m);
2✔
938
                if (err != NULL) {
2✔
UNCOV
939
                        bc_log_errorf("Failed to list units for node '%s': %s", node_name, err->message);
×
UNCOV
940
                        return -sd_bus_message_get_errno(m);
×
941
                }
942

943
                r = sd_bus_message_enter_container(m, SD_BUS_TYPE_ARRAY, UNIT_INFO_STRUCT_TYPESTRING);
2✔
944
                if (r < 0) {
2✔
945
                        return r;
946
                }
947

948
                while (sd_bus_message_at_end(m, false) == 0) {
230✔
949
                        r = sd_bus_message_open_container(
228✔
950
                                        reply, SD_BUS_TYPE_STRUCT, NODE_AND_UNIT_INFO_TYPESTRING);
951
                        if (r < 0) {
228✔
952
                                return r;
953
                        }
954

955
                        r = sd_bus_message_append(reply, "s", node_name);
228✔
956
                        if (r < 0) {
228✔
957
                                return r;
958
                        }
959

960
                        r = sd_bus_message_enter_container(m, SD_BUS_TYPE_STRUCT, UNIT_INFO_TYPESTRING);
228✔
961
                        if (r < 0) {
228✔
962
                                return r;
963
                        }
964

965
                        r = sd_bus_message_copy(reply, m, true);
228✔
966
                        if (r < 0) {
228✔
967
                                return r;
968
                        }
969

970
                        r = sd_bus_message_close_container(reply);
228✔
971
                        if (r < 0) {
228✔
972
                                return r;
973
                        }
974
                        r = sd_bus_message_exit_container(m);
228✔
975
                        if (r < 0) {
228✔
976
                                return r;
977
                        }
978
                }
979

980
                r = sd_bus_message_exit_container(m);
2✔
981
                if (r < 0) {
2✔
982
                        return r;
983
                }
984
        }
985

986
        r = sd_bus_message_close_container(reply);
1✔
987
        if (r < 0) {
1✔
UNCOV
988
                return r;
×
989
        }
990

991
        return 0;
992
}
993

994
static int controller_method_list_units(sd_bus_message *m, void *userdata, UNUSED sd_bus_error *ret_error) {
1✔
995
        Controller *controller = userdata;
1✔
996
        return agent_fleet_request_start(
1✔
997
                        m, controller, node_request_list_units, controller_method_list_units_encode_reply);
998
}
999

1000
/************************************************************************
1001
 ***** org.eclipse.bluechi.Controller.ListUnitFiles **************
1002
 ************************************************************************/
1003

1004
static int controller_method_list_unit_files_encode_reply(AgentFleetRequest *req, sd_bus_message *reply) {
1✔
1005
        int r = sd_bus_message_open_container(
1✔
1006
                        reply, SD_BUS_TYPE_ARRAY, NODE_AND_UNIT_FILE_INFO_STRUCT_TYPESTRING);
1007
        if (r < 0) {
1✔
1008
                return r;
1009
        }
1010

1011
        for (int i = 0; i < req->n_sub_req; i++) {
3✔
1012
                const char *node_name = req->sub_req[i].node->name;
2✔
1013
                sd_bus_message *m = req->sub_req[i].m;
2✔
1014
                if (m == NULL) {
2✔
UNCOV
1015
                        continue;
×
1016
                }
1017

1018
                const sd_bus_error *err = sd_bus_message_get_error(m);
2✔
1019
                if (err != NULL) {
2✔
UNCOV
1020
                        bc_log_errorf("Failed to list unit files for node '%s': %s", node_name, err->message);
×
UNCOV
1021
                        return -sd_bus_message_get_errno(m);
×
1022
                }
1023

1024
                r = sd_bus_message_enter_container(m, SD_BUS_TYPE_ARRAY, UNIT_FILE_INFO_STRUCT_TYPESTRING);
2✔
1025
                if (r < 0) {
2✔
1026
                        return r;
1027
                }
1028

1029
                while (sd_bus_message_at_end(m, false) == 0) {
292✔
1030
                        r = sd_bus_message_open_container(
290✔
1031
                                        reply, SD_BUS_TYPE_STRUCT, NODE_AND_UNIT_FILE_INFO_TYPESTRING);
1032
                        if (r < 0) {
290✔
1033
                                return r;
1034
                        }
1035

1036
                        r = sd_bus_message_append(reply, "s", node_name);
290✔
1037
                        if (r < 0) {
290✔
1038
                                return r;
1039
                        }
1040

1041
                        r = sd_bus_message_enter_container(m, SD_BUS_TYPE_STRUCT, UNIT_FILE_INFO_TYPESTRING);
290✔
1042
                        if (r < 0) {
290✔
1043
                                return r;
1044
                        }
1045

1046
                        r = sd_bus_message_copy(reply, m, true);
290✔
1047
                        if (r < 0) {
290✔
1048
                                return r;
1049
                        }
1050

1051
                        r = sd_bus_message_close_container(reply);
290✔
1052
                        if (r < 0) {
290✔
1053
                                return r;
1054
                        }
1055

1056
                        r = sd_bus_message_exit_container(m);
290✔
1057
                        if (r < 0) {
290✔
1058
                                return r;
1059
                        }
1060
                }
1061

1062
                r = sd_bus_message_exit_container(m);
2✔
1063
                if (r < 0) {
2✔
1064
                        return r;
1065
                }
1066
        }
1067

1068
        r = sd_bus_message_close_container(reply);
1✔
1069
        if (r < 0) {
1✔
UNCOV
1070
                return r;
×
1071
        }
1072

1073
        return 0;
1074
}
1075

1076
static int controller_method_list_unit_files(sd_bus_message *m, void *userdata, UNUSED sd_bus_error *ret_error) {
1✔
1077
        Controller *controller = userdata;
1✔
1078
        return agent_fleet_request_start(
1✔
1079
                        m,
1080
                        controller,
1081
                        node_request_list_unit_files,
1082
                        controller_method_list_unit_files_encode_reply);
1083
}
1084

1085
/************************************************************************
1086
 ***** org.eclipse.bluechi.Controller.ListNodes **************
1087
 ************************************************************************/
1088

1089
static int controller_method_list_encode_node(sd_bus_message *reply, Node *node) {
22✔
1090
        int r = sd_bus_message_open_container(reply, SD_BUS_TYPE_STRUCT, "soss");
22✔
1091
        if (r < 0) {
22✔
1092
                return r;
1093
        }
1094

1095
        r = sd_bus_message_append(
22✔
1096
                        reply, "soss", node->name, node->object_path, node_get_status(node), node->peer_ip);
1097
        if (r < 0) {
22✔
1098
                return r;
1099
        }
1100
        return sd_bus_message_close_container(reply);
22✔
1101
}
1102

1103
static int controller_method_list_nodes(sd_bus_message *m, void *userdata, UNUSED sd_bus_error *ret_error) {
15✔
1104
        Controller *controller = userdata;
15✔
1105
        _cleanup_sd_bus_message_ sd_bus_message *reply = NULL;
15✔
1106
        Node *node = NULL;
15✔
1107

1108
        int r = sd_bus_message_new_method_return(m, &reply);
15✔
1109
        if (r < 0) {
15✔
UNCOV
1110
                return sd_bus_reply_method_errorf(
×
1111
                                reply,
1112
                                SD_BUS_ERROR_FAILED,
1113
                                "Failed to create a reply message: %s",
1114
                                strerror(-r));
1115
        }
1116

1117
        r = sd_bus_message_open_container(reply, SD_BUS_TYPE_ARRAY, "(soss)");
15✔
1118
        if (r < 0) {
15✔
UNCOV
1119
                return sd_bus_reply_method_errorf(
×
1120
                                reply,
1121
                                SD_BUS_ERROR_INVALID_ARGS,
1122
                                "Invalid argument for the reply message: %s",
1123
                                strerror(-r));
1124
        }
1125

1126
        LIST_FOREACH(nodes, node, controller->nodes) {
37✔
1127
                r = controller_method_list_encode_node(reply, node);
22✔
1128
                if (r < 0) {
22✔
UNCOV
1129
                        return sd_bus_reply_method_errorf(
×
1130
                                        reply, SD_BUS_ERROR_FAILED, "Failed to encode a node: %s", strerror(-r));
1131
                }
1132
        }
1133

1134
        r = sd_bus_message_close_container(reply);
15✔
1135
        if (r < 0) {
15✔
UNCOV
1136
                return sd_bus_reply_method_errorf(
×
1137
                                reply, SD_BUS_ERROR_FAILED, "Failed to close message: %s", strerror(-r));
1138
        }
1139

1140
        return sd_bus_message_send(reply);
15✔
1141
}
1142

1143
/************************************************************************
1144
 **** org.eclipse.bluechi.Controller.GetNode *****************
1145
 ************************************************************************/
1146

1147
static int controller_method_get_node(sd_bus_message *m, void *userdata, UNUSED sd_bus_error *ret_error) {
39✔
1148
        Controller *controller = userdata;
39✔
1149
        _cleanup_sd_bus_message_ sd_bus_message *reply = NULL;
39✔
1150
        Node *node = NULL;
39✔
1151
        const char *node_name = NULL;
39✔
1152

1153
        int r = sd_bus_message_read(m, "s", &node_name);
39✔
1154
        if (r < 0) {
39✔
UNCOV
1155
                return sd_bus_reply_method_errorf(
×
1156
                                m, SD_BUS_ERROR_INVALID_ARGS, "Invalid argument for the node name");
1157
        }
1158

1159
        node = controller_find_node(controller, node_name);
39✔
1160
        if (node == NULL) {
39✔
UNCOV
1161
                return sd_bus_reply_method_errorf(m, SD_BUS_ERROR_SERVICE_UNKNOWN, "Node not found");
×
1162
        }
1163

1164
        r = sd_bus_message_new_method_return(m, &reply);
39✔
1165
        if (r < 0) {
39✔
UNCOV
1166
                return sd_bus_reply_method_errorf(
×
1167
                                reply,
1168
                                SD_BUS_ERROR_FAILED,
1169
                                "Failed to create a reply message: %s",
1170
                                strerror(-r));
1171
        }
1172

1173
        r = sd_bus_message_append(reply, "o", node->object_path);
39✔
1174
        if (r < 0) {
39✔
UNCOV
1175
                return sd_bus_reply_method_errorf(
×
1176
                                reply,
1177
                                SD_BUS_ERROR_FAILED,
1178
                                "Failed to append the object path of the node to the reply message: %s",
1179
                                strerror(-r));
1180
        }
1181

1182
        return sd_bus_message_send(reply);
39✔
1183
}
1184

1185
/************************************************************************
1186
 ***** org.eclipse.bluechi.Controller.CreateMonitor **********
1187
 ************************************************************************/
1188

1189
static int controller_method_create_monitor(sd_bus_message *m, void *userdata, UNUSED sd_bus_error *ret_error) {
15✔
1190
        Controller *controller = userdata;
15✔
1191
        _cleanup_sd_bus_message_ sd_bus_message *reply = NULL;
15✔
1192

1193
        _cleanup_monitor_ Monitor *monitor = monitor_new(controller, sd_bus_message_get_sender(m));
30✔
1194
        if (monitor == NULL) {
15✔
UNCOV
1195
                return sd_bus_reply_method_errorf(reply, SD_BUS_ERROR_FAILED, "Failed to create new monitor");
×
1196
        }
1197

1198
        if (!monitor_export(monitor)) {
15✔
UNCOV
1199
                return sd_bus_reply_method_errorf(reply, SD_BUS_ERROR_FAILED, "Failed to export monitor");
×
1200
        }
1201

1202
        int r = sd_bus_message_new_method_return(m, &reply);
15✔
1203
        if (r < 0) {
15✔
UNCOV
1204
                return sd_bus_reply_method_errorf(
×
1205
                                reply,
1206
                                SD_BUS_ERROR_FAILED,
1207
                                "Failed to create a reply message for the monitor: %s",
1208
                                strerror(-r));
1209
        }
1210

1211
        r = sd_bus_message_append(reply, "o", monitor->object_path);
15✔
1212
        if (r < 0) {
15✔
UNCOV
1213
                return sd_bus_reply_method_errorf(
×
1214
                                reply,
1215
                                SD_BUS_ERROR_FAILED,
1216
                                "Failed to append the object path of the monitor to the reply message: %s",
1217
                                strerror(-r));
1218
        }
1219

1220
        r = sd_bus_message_send(reply);
15✔
1221
        if (r < 0) {
15✔
1222
                return r;
1223
        }
1224

1225
        /* We reported it to the client, now keep it alive and keep track of it */
1226
        LIST_APPEND(monitors, controller->monitors, monitor_ref(monitor));
15✔
1227
        return 1;
1228
}
1229

1230
/************************************************************************
1231
 ***** org.eclipse.bluechi.Controller.EnableMetrics **********
1232
 ************************************************************************/
1233
static int controller_method_metrics_enable(sd_bus_message *m, void *userdata, UNUSED sd_bus_error *ret_error) {
3✔
1234
        Controller *controller = userdata;
3✔
1235
        Node *node = NULL;
3✔
1236
        int r = 0;
3✔
1237
        if (controller->metrics_enabled) {
3✔
UNCOV
1238
                return sd_bus_reply_method_errorf(
×
1239
                                m, SD_BUS_ERROR_INCONSISTENT_MESSAGE, "Metrics already enabled");
1240
        }
1241
        r = metrics_export(controller);
3✔
1242
        if (r < 0) {
3✔
UNCOV
1243
                return sd_bus_reply_method_errorf(
×
1244
                                m, SD_BUS_ERROR_FAILED, "Failed to register metrics service: %s", strerror(-r));
1245
        }
1246
        controller->metrics_enabled = true;
3✔
1247
        LIST_FOREACH(nodes, node, controller->nodes) {
6✔
1248
                node_enable_metrics(node);
3✔
1249
        }
1250
        bc_log_debug("Metrics enabled");
3✔
1251
        return sd_bus_reply_method_return(m, "");
3✔
1252
}
1253

1254
/************************************************************************
1255
 ***** org.eclipse.bluechi.Controller.DisableMetrics *********
1256
 ************************************************************************/
1257
static int controller_method_metrics_disable(sd_bus_message *m, void *userdata, UNUSED sd_bus_error *ret_error) {
1✔
1258
        Controller *controller = userdata;
1✔
1259
        Node *node = NULL;
1✔
1260
        if (!controller->metrics_enabled) {
1✔
UNCOV
1261
                return sd_bus_reply_method_errorf(
×
1262
                                m, SD_BUS_ERROR_INCONSISTENT_MESSAGE, "Metrics already disabled");
1263
        }
1264
        sd_bus_slot_unrefp(&controller->metrics_slot);
1✔
1265
        controller->metrics_slot = NULL;
1✔
1266
        controller->metrics_enabled = false;
1✔
1267
        LIST_FOREACH(nodes, node, controller->nodes) {
2✔
1268
                node_disable_metrics(node);
1✔
1269
        }
1270
        bc_log_debug("Metrics disabled");
1✔
1271
        return sd_bus_reply_method_return(m, "");
1✔
1272
}
1273

1274
/*************************************************************************
1275
 *** org.eclipse.bluechi.Controller.SetLogLevel ***************
1276
 *************************************************************************/
1277

1278
static int controller_method_set_log_level(
3✔
1279
                sd_bus_message *m, UNUSED void *userdata, UNUSED sd_bus_error *ret_error) {
1280
        const char *level = NULL;
3✔
1281

1282
        int r = sd_bus_message_read(m, "s", &level);
3✔
1283
        if (r < 0) {
3✔
UNCOV
1284
                bc_log_errorf("Failed to read the parameter: %s", strerror(-r));
×
UNCOV
1285
                return sd_bus_reply_method_errorf(
×
1286
                                m, SD_BUS_ERROR_FAILED, "Failed to read the parameter: %s", strerror(-r));
1287
        }
1288
        LogLevel loglevel = string_to_log_level(level);
3✔
1289
        if (loglevel == LOG_LEVEL_INVALID) {
3✔
1290
                bc_log_errorf("Invalid input for log level: %s", level);
1✔
1291
                return sd_bus_reply_method_errorf(m, SD_BUS_ERROR_INVALID_ARGS, "Invalid input for log level");
1✔
1292
        }
1293
        bc_log_set_level(loglevel);
2✔
1294
        bc_log_infof("Log level changed to %s", level);
2✔
1295
        return sd_bus_reply_method_return(m, "");
2✔
1296
}
1297

1298
/********************************************************
1299
 **** org.eclipse.bluechi.Controller.Status ****************
1300
 ********************************************************/
1301

1302
static char *controller_get_system_status(Controller *controller) {
255✔
1303
        if (controller->number_of_nodes_online == 0) {
255✔
1304
                return "down";
1305
        } else if (controller->number_of_nodes_online == controller->number_of_nodes) {
155✔
1306
                return "up";
98✔
1307
        }
1308
        return "degraded";
1309
}
1310

1311
void controller_check_system_status(Controller *controller, int prev_number_of_nodes_online) {
269✔
1312
        int diff = controller->number_of_nodes_online - prev_number_of_nodes_online;
269✔
1313
        // clang-format off
1314
        if ((prev_number_of_nodes_online == 0) ||                                          // at least one node online
269✔
1315
                (prev_number_of_nodes_online == controller->number_of_nodes) ||            // at least one node offline
162✔
1316
                ((prev_number_of_nodes_online + diff) == controller->number_of_nodes) ||   // all nodes online
47✔
1317
                ((prev_number_of_nodes_online + diff) == 0)) {                             // all nodes offline
1318
                // clang-format on
1319
                int r = sd_bus_emit_properties_changed(
249✔
1320
                                controller->api_bus,
1321
                                BC_CONTROLLER_OBJECT_PATH,
1322
                                CONTROLLER_INTERFACE,
1323
                                "Status",
1324
                                NULL);
1325
                if (r < 0) {
249✔
UNCOV
1326
                        bc_log_errorf("Failed to emit status property changed: %s", strerror(-r));
×
1327
                }
1328
        }
1329
}
269✔
1330

1331
static int controller_property_get_status(
255✔
1332
                UNUSED sd_bus *bus,
1333
                UNUSED const char *path,
1334
                UNUSED const char *interface,
1335
                UNUSED const char *property,
1336
                sd_bus_message *reply,
1337
                void *userdata,
1338
                UNUSED sd_bus_error *ret_error) {
1339
        Controller *controller = userdata;
255✔
1340

1341
        return sd_bus_message_append(reply, "s", controller_get_system_status(controller));
255✔
1342
}
1343

1344
static int controller_property_get_loglevel(
2✔
1345
                UNUSED sd_bus *bus,
1346
                UNUSED const char *path,
1347
                UNUSED const char *interface,
1348
                UNUSED const char *property,
1349
                sd_bus_message *reply,
1350
                UNUSED void *userdata,
1351
                UNUSED sd_bus_error *ret_error) {
1352
        const char *log_level = log_level_to_string(bc_log_get_level());
2✔
1353
        return sd_bus_message_append(reply, "s", log_level);
2✔
1354
}
1355

1356
static int controller_property_get_log_target(
1✔
1357
                UNUSED sd_bus *bus,
1358
                UNUSED const char *path,
1359
                UNUSED const char *interface,
1360
                UNUSED const char *property,
1361
                sd_bus_message *reply,
1362
                UNUSED void *userdata,
1363
                UNUSED sd_bus_error *ret_error) {
1364
        return sd_bus_message_append(reply, "s", log_target_to_str(bc_log_get_log_fn()));
1✔
1365
}
1366

1367
static const sd_bus_vtable controller_vtable[] = {
1368
        SD_BUS_VTABLE_START(0),
1369
        SD_BUS_METHOD("ListUnits", "", NODE_AND_UNIT_INFO_STRUCT_ARRAY_TYPESTRING, controller_method_list_units, 0),
1370
        SD_BUS_METHOD("ListUnitFiles",
1371
                      "",
1372
                      NODE_AND_UNIT_FILE_INFO_STRUCT_ARRAY_TYPESTRING,
1373
                      controller_method_list_unit_files,
1374
                      0),
1375
        SD_BUS_METHOD("ListNodes", "", "a(soss)", controller_method_list_nodes, 0),
1376
        SD_BUS_METHOD("GetNode", "s", "o", controller_method_get_node, 0),
1377
        SD_BUS_METHOD("CreateMonitor", "", "o", controller_method_create_monitor, 0),
1378
        SD_BUS_METHOD("SetLogLevel", "s", "", controller_method_set_log_level, 0),
1379
        SD_BUS_METHOD("EnableMetrics", "", "", controller_method_metrics_enable, 0),
1380
        SD_BUS_METHOD("DisableMetrics", "", "", controller_method_metrics_disable, 0),
1381
        SD_BUS_SIGNAL_WITH_NAMES("JobNew", "uo", SD_BUS_PARAM(id) SD_BUS_PARAM(job), 0),
1382
        SD_BUS_SIGNAL_WITH_NAMES(
1383
                        "JobRemoved",
1384
                        "uosss",
1385
                        SD_BUS_PARAM(id) SD_BUS_PARAM(job) SD_BUS_PARAM(node) SD_BUS_PARAM(unit)
1386
                                        SD_BUS_PARAM(result),
1387
                        0),
1388
        SD_BUS_PROPERTY("LogLevel", "s", controller_property_get_loglevel, 0, SD_BUS_VTABLE_PROPERTY_EXPLICIT),
1389
        SD_BUS_PROPERTY("LogTarget", "s", controller_property_get_log_target, 0, SD_BUS_VTABLE_PROPERTY_CONST),
1390
        SD_BUS_PROPERTY("Status", "s", controller_property_get_status, 0, SD_BUS_VTABLE_PROPERTY_EMITS_CHANGE),
1391
        SD_BUS_VTABLE_END
1392
};
1393

1394
static int controller_dbus_filter(UNUSED sd_bus_message *m, void *userdata, UNUSED sd_bus_error *ret_error) {
958✔
1395
        Controller *controller = userdata;
958✔
1396
        const char *object_path = sd_bus_message_get_path(m);
958✔
1397
        const char *iface = sd_bus_message_get_interface(m);
958✔
1398

1399
        if (DEBUG_MESSAGES) {
958✔
1400
                bc_log_infof("Incoming public message: path: %s, iface: %s, member: %s, signature: '%s'",
1401
                             object_path,
1402
                             iface,
1403
                             sd_bus_message_get_member(m),
1404
                             sd_bus_message_get_signature(m, true));
1405
        }
1406

1407
        if (iface != NULL && streq(iface, NODE_INTERFACE)) {
958✔
1408
                Node *node = controller_find_node_by_path(controller, object_path);
112✔
1409

1410
                /* All Node interface objects fail if the node is offline */
1411
                if (node && !node_has_agent(node)) {
112✔
UNCOV
1412
                        return sd_bus_reply_method_errorf(m, BC_BUS_ERROR_OFFLINE, "Node is offline");
×
1413
                }
1414
        }
1415

1416
        return 0;
1417
}
1418

1419
void controller_remove_monitor(Controller *controller, Monitor *monitor) {
15✔
1420
        LIST_REMOVE(monitors, controller->monitors, monitor);
15✔
1421
        monitor_unref(monitor);
15✔
1422
}
15✔
1423

1424
static void controller_client_disconnected(Controller *controller, const char *client_id) {
168✔
1425
        /* Free any monitors owned by the client */
1426

1427
        Monitor *monitor = NULL;
168✔
1428
        Monitor *next_monitor = NULL;
168✔
1429
        LIST_FOREACH_SAFE(monitors, monitor, next_monitor, controller->monitors) {
187✔
1430
                if (streq(monitor->owner, client_id)) {
19✔
1431
                        monitor_close(monitor);
14✔
1432
                        controller_remove_monitor(controller, monitor);
14✔
1433
                }
1434
        }
1435
}
168✔
1436

1437
static int controller_name_owner_changed(sd_bus_message *m, void *userdata, UNUSED sd_bus_error *ret_error) {
348✔
1438
        Controller *controller = userdata;
348✔
1439
        const char *name = NULL;
348✔
1440
        const char *old_owner = NULL;
348✔
1441
        const char *new_owner = NULL;
348✔
1442

1443
        int r = sd_bus_message_read(m, "sss", &name, &old_owner, &new_owner);
348✔
1444
        if (r < 0) {
348✔
1445
                return r;
348✔
1446
        }
1447

1448
        if (*name == ':' && *new_owner == 0) {
348✔
1449
                controller_client_disconnected(controller, name);
168✔
1450
        }
1451

1452
        return 0;
1453
}
1454

1455
bool controller_start(Controller *controller) {
108✔
1456
        bc_log_infof("Starting bluechi-controller %s", CONFIG_H_BC_VERSION);
108✔
1457
        if (controller == NULL) {
108✔
1458
                return false;
108✔
1459
        }
1460

1461
#ifdef USE_USER_API_BUS
1462
        controller->api_bus = user_bus_open(controller->event);
1463
#else
1464
        controller->api_bus = system_bus_open(controller->event);
108✔
1465
#endif
1466
        if (controller->api_bus == NULL) {
108✔
UNCOV
1467
                bc_log_error("Failed to open api dbus");
×
UNCOV
1468
                return false;
×
1469
        }
1470

1471
        /* Export all known nodes */
1472
        Node *node = NULL;
108✔
1473
        LIST_FOREACH(nodes, node, controller->nodes) {
390✔
1474
                if (!node_export(node)) {
282✔
1475
                        return false;
1476
                }
1477
        }
1478

1479
        int r = sd_bus_request_name(
216✔
1480
                        controller->api_bus, controller->api_bus_service_name, SD_BUS_NAME_REPLACE_EXISTING);
108✔
1481
        if (r < 0) {
108✔
UNCOV
1482
                bc_log_errorf("Failed to acquire service name on api dbus: %s", strerror(-r));
×
UNCOV
1483
                return false;
×
1484
        }
1485

1486
        r = sd_bus_add_filter(
108✔
1487
                        controller->api_bus, &controller->filter_slot, controller_dbus_filter, controller);
1488
        if (r < 0) {
108✔
UNCOV
1489
                bc_log_errorf("Failed to add controller filter: %s", strerror(-r));
×
UNCOV
1490
                return false;
×
1491
        }
1492

1493
        r = sd_bus_match_signal(
108✔
1494
                        controller->api_bus,
1495
                        &controller->name_owner_changed_slot,
1496
                        "org.freedesktop.DBus",
1497
                        "/org/freedesktop/DBus",
1498
                        "org.freedesktop.DBus",
1499
                        "NameOwnerChanged",
1500
                        controller_name_owner_changed,
1501
                        controller);
1502
        if (r < 0) {
108✔
UNCOV
1503
                bc_log_errorf("Failed to add nameloist filter: %s", strerror(-r));
×
UNCOV
1504
                return false;
×
1505
        }
1506

1507
        r = sd_bus_add_object_vtable(
108✔
1508
                        controller->api_bus,
1509
                        &controller->controller_slot,
1510
                        BC_CONTROLLER_OBJECT_PATH,
1511
                        CONTROLLER_INTERFACE,
1512
                        controller_vtable,
1513
                        controller);
1514
        if (r < 0) {
108✔
UNCOV
1515
                bc_log_errorf("Failed to add controller vtable: %s", strerror(-r));
×
UNCOV
1516
                return false;
×
1517
        }
1518

1519
        if (!controller_setup_node_connection_handler(controller)) {
108✔
1520
                return false;
1521
        }
1522

1523
        r = controller_setup_heartbeat_timer(controller);
108✔
1524
        if (r < 0) {
108✔
UNCOV
1525
                bc_log_errorf("Failed to set up controller heartbeat timer: %s", strerror(-r));
×
UNCOV
1526
                return false;
×
1527
        }
1528

1529
        ShutdownHook hook;
108✔
1530
        hook.shutdown = (ShutdownHookFn) controller_stop;
108✔
1531
        hook.userdata = controller;
108✔
1532
        r = event_loop_add_shutdown_signals(controller->event, &hook);
108✔
1533
        if (r < 0) {
108✔
UNCOV
1534
                bc_log_errorf("Failed to add signals to controller event loop: %s", strerror(-r));
×
1535
                return false;
×
1536
        }
1537

1538
        r = sd_event_loop(controller->event);
108✔
1539
        if (r < 0) {
108✔
UNCOV
1540
                bc_log_errorf("Starting event loop failed: %s", strerror(-r));
×
1541
                return false;
×
1542
        }
1543

1544
        return true;
1545
}
1546

1547
void controller_stop(Controller *controller) {
108✔
1548
        if (controller == NULL) {
108✔
1549
                return;
1550
        }
1551

1552
        bc_log_debug("Stopping controller");
108✔
1553

1554
        Job *job = NULL;
108✔
1555
        Job *next_job = NULL;
108✔
1556
        LIST_FOREACH_SAFE(jobs, job, next_job, controller->jobs) {
110✔
1557
                controller_remove_job(controller, job, "cancelled due to shutdown");
2✔
1558
        }
1559

1560
        Subscription *sub = NULL;
108✔
1561
        Subscription *next_sub = NULL;
108✔
1562
        LIST_FOREACH_SAFE(all_subscriptions, sub, next_sub, controller->all_subscriptions) {
108✔
UNCOV
1563
                controller_remove_subscription(controller, sub);
×
1564
        }
1565

1566
        Monitor *monitor = NULL;
108✔
1567
        Monitor *next_monitor = NULL;
108✔
1568
        LIST_FOREACH_SAFE(monitors, monitor, next_monitor, controller->monitors) {
108✔
UNCOV
1569
                controller_remove_monitor(controller, monitor);
×
1570
        }
1571

1572
        /* If all nodes were already offline, we don't need to emit a changed signal */
1573
        bool status_changed = controller->number_of_nodes_online > 0;
108✔
1574

1575
        Node *node = NULL;
108✔
1576
        Node *next_node = NULL;
108✔
1577
        LIST_FOREACH_SAFE(nodes, node, next_node, controller->nodes) {
390✔
1578
                controller_remove_node(controller, node);
282✔
1579
        }
1580
        LIST_FOREACH_SAFE(nodes, node, next_node, controller->anonymous_nodes) {
243✔
1581
                controller_remove_node(controller, node);
135✔
1582
        }
1583

1584
        /*
1585
         * We won't handle any other events incl. node disconnected since we exit the event loop
1586
         * right afterwards. Therefore, check the controller state and emit signal here.
1587
         */
1588
        if (status_changed) {
108✔
1589
                controller_check_system_status(controller, controller->number_of_nodes_online);
9✔
1590
        }
1591
}
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

© 2026 Coveralls, Inc