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

eclipse-bluechi / bluechi / 15045056381

15 May 2025 12:33PM UTC coverage: 37.505% (-44.9%) from 82.405%
15045056381

Pull #1072

github

web-flow
Merge b13cd68af into 04834083b
Pull Request #1072: Prevent tmt from pruning coverage files

1888 of 5034 relevant lines covered (37.5%)

23.81 hits per line

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

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

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

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

67
        return controller;
68
}
69

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

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

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

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

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

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

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

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

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

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

124
        free(controller);
1✔
125
}
126

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

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

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

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

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

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

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

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

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

174
        return NULL;
175
}
176

177

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

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

187
        return NULL;
188
}
189

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

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

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

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

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

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

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

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

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

288
        return steal_pointer(&node);
289
}
290

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

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

301

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

400
        _cleanup_freev_ char **sections = cfg_list_sections(controller->config);
2✔
401
        if (sections == NULL) {
1✔
402
                bc_log_error("Failed to list config sections");
×
403
                return false;
404
        }
405
        for (size_t i = 0; sections[i] != NULL; i++) {
2✔
406
                const char *section = sections[i];
1✔
407
                if (!str_has_prefix(section, CFG_SECT_NODE_PREFIX)) {
1✔
408
                        continue;
1✔
409
                }
410
                const char *node_name = section + strlen(CFG_SECT_NODE_PREFIX);
×
411
                Node *node = controller_find_node(controller, node_name);
×
412
                if (node == NULL) {
×
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(
×
422
                                controller->config, section, CFG_REQUIRED_SELINUX_CONTEXT);
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);
1✔
429
        if (interval_msec) {
1✔
430
                if (!controller_set_heartbeat_interval(controller, interval_msec)) {
1✔
431
                        return false;
432
                }
433
        }
434

435
        const char *threshold_msec = cfg_get_value(controller->config, CFG_NODE_HEARTBEAT_THRESHOLD);
1✔
436
        if (threshold_msec) {
1✔
437
                if (!controller_set_heartbeat_threshold(controller, threshold_msec)) {
1✔
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);
1✔
444
        if (keepidle) {
1✔
445
                if (socket_options_set_tcp_keepidle(controller->peer_socket_options, keepidle) < 0) {
1✔
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);
1✔
451
        if (keepintvl) {
1✔
452
                if (socket_options_set_tcp_keepintvl(controller->peer_socket_options, keepintvl) < 0) {
1✔
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);
1✔
458
        if (keepcnt) {
1✔
459
                if (socket_options_set_tcp_keepcnt(controller->peer_socket_options, keepcnt) < 0) {
1✔
460
                        bc_log_error("Failed to set TCP KEEPCNT");
×
461
                        return false;
462
                }
463
        }
464
        if (socket_options_set_ip_recverr(
1✔
465
                            controller->peer_socket_options,
466
                            cfg_get_bool_value(controller->config, CFG_IP_RECEIVE_ERRORS)) < 0) {
1✔
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(
2✔
475
                UNUSED sd_event_source *source, int fd, UNUSED uint32_t revents, void *userdata) {
476
        Controller *controller = userdata;
2✔
477
        Node *node = NULL;
2✔
478
        _cleanup_fd_ int nfd = accept_connection_request(fd);
2✔
479
        if (nfd < 0) {
2✔
480
                bc_log_errorf("Failed to accept connection request: %s", strerror(-nfd));
×
481
                return -1;
×
482
        }
483

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

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

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

498
        if (!node_set_agent_bus(node, dbus_server)) {
2✔
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) {
1✔
507
        int r = 0;
1✔
508
        _cleanup_sd_event_source_ sd_event_source *event_source = NULL;
1✔
509

510
        int n = sd_listen_fds(0);
1✔
511
        if (n < 1) {
1✔
512
                bc_log_debug("No socket unit file descriptor has been passed");
1✔
513
                return true;
514
        }
515
        if (n > 1) {
×
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(
×
521
                        controller->event,
522
                        &event_source,
523
                        SD_LISTEN_FDS_START,
524
                        EPOLLIN,
525
                        controller_accept_node_connection,
526
                        controller);
527
        if (r < 0) {
×
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);
×
532
        if (r < 0) {
×
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");
×
538
        controller->node_connection_systemd_socket_source = steal_pointer(&event_source);
×
539

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

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

549
        tcp_fd = create_tcp_socket(controller->port);
1✔
550
        if (tcp_fd < 0) {
1✔
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
                 */
556
                if (tcp_fd == -EADDRINUSE && controller->node_connection_systemd_socket_source != NULL &&
×
557
                    sd_is_socket_inet(SD_LISTEN_FDS_START, AF_UNSPEC, SOCK_STREAM, 1, controller->port) > 0) {
×
558
                        bc_log_warnf("TCP socket for port %d already setup with systemd socket unit",
×
559
                                     controller->port);
560
                        return true;
561
                }
562

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

567
        r = sd_event_add_io(
1✔
568
                        controller->event,
569
                        &event_source,
570
                        tcp_fd,
571
                        EPOLLIN,
572
                        controller_accept_node_connection,
573
                        controller);
574
        if (r < 0) {
1✔
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);
1✔
579
        if (r < 0) {
1✔
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);
1✔
585

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

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

593

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

599
        uds_fd = create_uds_socket(CONFIG_H_UDS_SOCKET_PATH);
1✔
600
        if (uds_fd < 0) {
1✔
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 &&
×
607
                    sd_is_socket_unix(SD_LISTEN_FDS_START, AF_UNIX, 1, CONFIG_H_UDS_SOCKET_PATH, 0) > 0) {
×
608
                        bc_log_warnf("UDS socket for path %s already setup with systemd socket unit",
×
609
                                     CONFIG_H_UDS_SOCKET_PATH);
610
                        return true;
611
                } else if (uds_fd == -EADDRINUSE) {
×
612
                        /* If address is in use, remove socket file and retry again */
613
                        unlink(CONFIG_H_UDS_SOCKET_PATH);
×
614
                        uds_fd = create_uds_socket(CONFIG_H_UDS_SOCKET_PATH);
×
615
                        if (uds_fd < 0) {
×
616
                                bc_log_errorf("Failed to create UDS socket: %s", strerror(-uds_fd));
×
617
                                return false;
618
                        }
619
                } else {
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(
1✔
626
                        controller->event,
627
                        &event_source,
628
                        uds_fd,
629
                        EPOLLIN,
630
                        controller_accept_node_connection,
631
                        controller);
632
        if (r < 0) {
1✔
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);
1✔
637
        if (r < 0) {
1✔
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);
1✔
643

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

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

651

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

663
        if (controller->node_connection_systemd_socket_source == NULL &&
1✔
664
            controller->node_connection_tcp_socket_source == NULL &&
1✔
665
            controller->node_connection_uds_socket_source == NULL) {
×
666
                bc_log_error("No connection request handler configured");
×
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) {
×
675
        uint64_t diff = 0;
×
676

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

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

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

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

701
        return true;
702
}
703

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

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

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

720
                r = sd_bus_emit_signal(
×
721
                                node->agent_bus,
722
                                INTERNAL_CONTROLLER_OBJECT_PATH,
723
                                INTERNAL_CONTROLLER_INTERFACE,
724
                                "Heartbeat",
725
                                "");
726
                if (r < 0) {
×
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);
×
734
        if (r < 0) {
×
735
                bc_log_errorf("Failed to reset controller heartbeat timer: %s", strerror(-r));
×
736
                return r;
×
737
        }
738

739
        return 0;
740
}
741

742
static int controller_reset_heartbeat_timer(Controller *controller, sd_event_source **event_source) {
×
743
        return event_reset_time_relative(
×
744
                        controller->event,
745
                        event_source,
746
                        CLOCK_BOOTTIME,
747
                        controller->heartbeat_interval_msec * USEC_PER_MSEC,
×
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) {
1✔
757
        _cleanup_(sd_event_source_unrefp) sd_event_source *event_source = NULL;
1✔
758
        int r = 0;
1✔
759

760
        assert(controller);
1✔
761

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

768
        r = controller_reset_heartbeat_timer(controller, &event_source);
×
769
        if (r < 0) {
×
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);
×
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) {
×
801
        sd_bus_message_unref(req->request_message);
×
802

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

809
        free(req);
×
810
}
×
811

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

819
#define _cleanup_agent_fleet_request_ _cleanup_(agent_fleet_request_freep)
820

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

825
        _cleanup_sd_bus_message_ sd_bus_message *reply = NULL;
×
826
        int r = sd_bus_message_new_method_return(req->request_message, &reply);
×
827
        if (r < 0) {
×
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);
×
837
        if (r < 0) {
×
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);
×
847
        if (r < 0) {
×
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) {
×
854
        if (req->n_done == req->n_sub_req) {
×
855
                agent_fleet_request_done(req);
×
856
        }
857
}
×
858

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

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

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

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

875
        agent_fleet_request_maybe_done(req);
×
876

877
        return 0;
×
878
}
879

880
static int agent_fleet_request_start(
×
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;
×
886

887
        req = malloc0_array(sizeof(*req), sizeof(req->sub_req[0]), controller->number_of_nodes);
×
888
        if (req == NULL) {
×
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);
×
892
        req->encode = encode;
×
893

894
        Node *node = NULL;
×
895
        int i = 0;
×
896
        LIST_FOREACH(nodes, node, controller->nodes) {
×
897
                _cleanup_agent_request_ AgentRequest *agent_req = create_request(
×
898
                                node, agent_fleet_request_callback, req, NULL);
899
                if (agent_req) {
×
900
                        req->sub_req[i].agent_req = steal_pointer(&agent_req);
×
901
                        req->sub_req[i].node = node_ref(node);
×
902
                        req->n_sub_req++;
×
903
                        i++;
×
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);
×
915

916
        return 1;
×
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) {
×
925
        int r = sd_bus_message_open_container(reply, SD_BUS_TYPE_ARRAY, NODE_AND_UNIT_INFO_DICT_TYPESTRING);
×
926
        if (r < 0) {
×
927
                return r;
928
        }
929

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

937
                const sd_bus_error *err = sd_bus_message_get_error(m);
×
938
                if (err != NULL) {
×
939
                        return -sd_bus_message_get_errno(m);
×
940
                }
941

942
                r = sd_bus_message_open_container(reply, SD_BUS_TYPE_DICT_ENTRY, NODE_AND_UNIT_INFO_TYPESTRING);
×
943
                if (r < 0) {
×
944
                        return r;
945
                }
946

947
                r = sd_bus_message_append(reply, "s", node_name);
×
948
                if (r < 0) {
×
949
                        return r;
950
                }
951

952
                r = sd_bus_message_open_container(reply, SD_BUS_TYPE_ARRAY, UNIT_INFO_STRUCT_TYPESTRING);
×
953
                if (r < 0) {
×
954
                        return r;
955
                }
956

957
                r = sd_bus_message_enter_container(m, SD_BUS_TYPE_ARRAY, UNIT_INFO_STRUCT_TYPESTRING);
×
958
                if (r < 0) {
×
959
                        return r;
960
                }
961

962
                while (sd_bus_message_at_end(m, false) == 0) {
×
963
                        r = sd_bus_message_copy(reply, m, true);
×
964
                        if (r < 0) {
×
965
                                return r;
966
                        }
967
                }
968

969
                r = sd_bus_message_close_container(reply);
×
970
                if (r < 0) {
×
971
                        return r;
972
                }
973

974
                r = sd_bus_message_close_container(reply);
×
975
                if (r < 0) {
×
976
                        return r;
977
                }
978

979
                r = sd_bus_message_exit_container(m);
×
980
                if (r < 0) {
×
981
                        return r;
982
                }
983
        }
984

985
        r = sd_bus_message_close_container(reply);
×
986
        if (r < 0) {
×
987
                return r;
×
988
        }
989

990
        return 0;
991
}
992

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

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

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

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

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

1023
                r = sd_bus_message_open_container(
×
1024
                                reply, SD_BUS_TYPE_DICT_ENTRY, NODE_AND_UNIT_FILE_INFO_TYPESTRING);
1025
                if (r < 0) {
×
1026
                        return r;
1027
                }
1028

1029
                r = sd_bus_message_append(reply, "s", node_name);
×
1030
                if (r < 0) {
×
1031
                        return r;
1032
                }
1033

1034
                r = sd_bus_message_open_container(reply, SD_BUS_TYPE_ARRAY, UNIT_FILE_INFO_STRUCT_TYPESTRING);
×
1035
                if (r < 0) {
×
1036
                        return r;
1037
                }
1038

1039
                r = sd_bus_message_enter_container(m, SD_BUS_TYPE_ARRAY, UNIT_FILE_INFO_STRUCT_TYPESTRING);
×
1040
                if (r < 0) {
×
1041
                        return r;
1042
                }
1043

1044
                while (sd_bus_message_at_end(m, false) == 0) {
×
1045
                        r = sd_bus_message_copy(reply, m, true);
×
1046
                        if (r < 0) {
×
1047
                                return r;
1048
                        }
1049
                }
1050

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

1056
                r = sd_bus_message_close_container(reply);
×
1057
                if (r < 0) {
×
1058
                        return r;
1059
                }
1060

1061
                r = sd_bus_message_exit_container(m);
×
1062
                if (r < 0) {
×
1063
                        return r;
1064
                }
1065
        }
1066

1067
        r = sd_bus_message_close_container(reply);
×
1068
        if (r < 0) {
×
1069
                return r;
×
1070
        }
1071

1072
        return 0;
1073
}
1074

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

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

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

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

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

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

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

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

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

1139
        return sd_bus_message_send(reply);
×
1140
}
1141

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

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

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

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

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

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

1181
        return sd_bus_message_send(reply);
×
1182
}
1183

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

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

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

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

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

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

1219
        r = sd_bus_message_send(reply);
×
1220
        if (r < 0) {
×
1221
                return r;
1222
        }
1223

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

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

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

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

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

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

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

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

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

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

1340
        return sd_bus_message_append(reply, "s", controller_get_system_status(controller));
4✔
1341
}
1342

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

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

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

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

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

1406
        if (iface != NULL && streq(iface, NODE_INTERFACE)) {
6✔
1407
                Node *node = controller_find_node_by_path(controller, object_path);
×
1408

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

1415
        return 0;
1416
}
1417

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

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

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

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

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

1447
        if (*name == ':' && *new_owner == 0) {
4✔
1448
                controller_client_disconnected(controller, name);
1✔
1449
        }
1450

1451
        return 0;
1452
}
1453

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

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

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

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

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

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

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

1518
        if (!controller_setup_node_connection_handler(controller)) {
1✔
1519
                return false;
1520
        }
1521

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

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

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

1543
        return true;
1544
}
1545

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

1551
        bc_log_debug("Stopping controller");
1✔
1552

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

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

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

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

1574
        Node *node = NULL;
1✔
1575
        Node *next_node = NULL;
1✔
1576
        LIST_FOREACH_SAFE(nodes, node, next_node, controller->nodes) {
3✔
1577
                controller_remove_node(controller, node);
2✔
1578
        }
1579
        LIST_FOREACH_SAFE(nodes, node, next_node, controller->anonymous_nodes) {
3✔
1580
                controller_remove_node(controller, node);
2✔
1581
        }
1582

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