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

eclipse-bluechi / bluechi / 15138809430

20 May 2025 01:26PM UTC coverage: 82.468% (+0.2%) from 82.302%
15138809430

Pull #1073

github

web-flow
Merge a7b402d67 into 567e8d47b
Pull Request #1073: Added whitelist for nodes of proxy services

35 of 36 new or added lines in 2 files covered. (97.22%)

2 existing lines in 2 files now uncovered.

5654 of 6856 relevant lines covered (82.47%)

1551.46 hits per line

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

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

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

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

67
        return controller;
68
}
69

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

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

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

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

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

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

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

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

105
                /* Remove UDS socket for proper cleanup and not cause an address in use error */
106
                unlink(CONFIG_H_UDS_SOCKET_PATH);
109✔
107
        }
108
        if (controller->node_connection_systemd_socket_source != NULL) {
125✔
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);
125✔
119
        sd_bus_slot_unrefp(&controller->filter_slot);
125✔
120
        sd_bus_slot_unrefp(&controller->controller_slot);
125✔
121
        sd_bus_slot_unrefp(&controller->metrics_slot);
125✔
122
        sd_bus_unrefp(&controller->api_bus);
125✔
123

124
        free(controller);
125✔
125
}
126

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

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

132
        if (subscription_has_node_wildcard(sub)) {
25✔
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);
23✔
140
        if (node) {
23✔
141
                node_subscribe(node, sub);
22✔
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) {
25✔
148
        Node *node = NULL;
25✔
149

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

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

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

168
        LIST_FOREACH(nodes, node, controller->nodes) {
12,471✔
169
                if (strcmp(node->name, name) == 0) {
12,101✔
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) {
134✔
182
                if (streq(node->object_path, path)) {
134✔
183
                        return node;
184
                }
185
        }
186

187
        return NULL;
188
}
189

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

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

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

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

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

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

281
        if (name) {
597✔
282
                controller->number_of_nodes++;
366✔
283
                LIST_APPEND(nodes, controller->nodes, node);
11,565✔
284
        } else {
285
                LIST_APPEND(nodes, controller->anonymous_nodes, node);
297✔
286
        }
287

288
        return steal_pointer(&node);
289
}
290

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

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

301

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

489
        return true;
490
}
491

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

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

508
        bus_socket_set_options(dbus_server, controller->peer_socket_options);
231✔
509

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

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

521
        return 0;
522
}
523

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

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

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

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

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

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

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

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

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

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

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

611

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

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

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

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

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

669

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

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

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

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

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

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

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

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

719
        return true;
720
}
721

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

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

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

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

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

757
        return 0;
758
}
759

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

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

778
        assert(controller);
111✔
779

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

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

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

795
/************************************************************************
796
 ***************** AgentFleetRequest ************************************
797
 ************************************************************************/
798

799
typedef struct AgentFleetRequest AgentFleetRequest;
800

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

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

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

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

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

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

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

837
#define _cleanup_agent_fleet_request_ _cleanup_(agent_fleet_request_freep)
838

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

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

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

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

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

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

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

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

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

893
        agent_fleet_request_maybe_done(req);
5✔
894

895
        return 0;
5✔
896
}
897

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

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

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

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

932
        agent_fleet_request_maybe_done(req);
2✔
933

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

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

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

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

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

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

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

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

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

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

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

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

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

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

1008
        return 0;
1009
}
1010

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

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

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

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

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

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

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

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

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

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

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

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

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

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

1090
        return 0;
1091
}
1092

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

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

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

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

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

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

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

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

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

1157
        return sd_bus_message_send(reply);
15✔
1158
}
1159

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

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

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

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

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

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

1199
        return sd_bus_message_send(reply);
39✔
1200
}
1201

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

1358
        return sd_bus_message_append(reply, "s", controller_get_system_status(controller));
428✔
1359
}
1360

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

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

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

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

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

1424
        if (iface != NULL && streq(iface, NODE_INTERFACE)) {
1,424✔
1425
                Node *node = controller_find_node_by_path(controller, object_path);
112✔
1426

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

1433
        return 0;
1434
}
1435

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

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

1444
        Monitor *monitor = NULL;
289✔
1445
        Monitor *next_monitor = NULL;
289✔
1446
        LIST_FOREACH_SAFE(monitors, monitor, next_monitor, controller->monitors) {
310✔
1447
                if (streq(monitor->owner, client_id)) {
21✔
1448
                        monitor_close(monitor);
15✔
1449
                        controller_remove_monitor(controller, monitor);
15✔
1450
                }
1451
        }
1452
}
289✔
1453

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

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

1465
        if (*name == ':' && *new_owner == 0) {
794✔
1466
                controller_client_disconnected(controller, name);
289✔
1467
        }
1468

1469
        return 0;
1470
}
1471

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

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

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

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

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

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

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

1536
        if (!controller_setup_node_connection_handler(controller)) {
111✔
1537
                return false;
1538
        }
1539

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

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

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

1561
        return true;
1562
}
1563

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

1569
        bc_log_debug("Stopping controller");
111✔
1570

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

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

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

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

1592
        Node *node = NULL;
111✔
1593
        Node *next_node = NULL;
111✔
1594
        LIST_FOREACH_SAFE(nodes, node, next_node, controller->nodes) {
477✔
1595
                controller_remove_node(controller, node);
366✔
1596
        }
1597
        LIST_FOREACH_SAFE(nodes, node, next_node, controller->anonymous_nodes) {
340✔
1598
                controller_remove_node(controller, node);
229✔
1599
        }
1600

1601
        /*
1602
         * We won't handle any other events incl. node disconnected since we exit the event loop
1603
         * right afterwards. Therefore, check the controller state and emit signal here.
1604
         */
1605
        if (status_changed) {
111✔
1606
                controller_check_system_status(controller, controller->number_of_nodes_online);
14✔
1607
        }
1608
}
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