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

eclipse-bluechi / bluechi / 13783699263

11 Mar 2025 08:38AM UTC coverage: 82.337% (-0.002%) from 82.339%
13783699263

push

github

mkemel
Added new config option ConnectionRetryCountUntilQuiet

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

12 of 15 new or added lines in 2 files covered. (80.0%)

210 existing lines in 2 files now uncovered.

5617 of 6822 relevant lines covered (82.34%)

1182.49 hits per line

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

82.38
/src/agent/agent.c
1
/*
2
 * Copyright Contributors to the Eclipse BlueChi project
3
 *
4
 * SPDX-License-Identifier: LGPL-2.1-or-later
5
 */
6
#include <arpa/inet.h>
7
#include <errno.h>
8
#include <stdio.h>
9
#include <string.h>
10

11
#include "libbluechi/bus/bus.h"
12
#include "libbluechi/bus/utils.h"
13
#include "libbluechi/common/cfg.h"
14
#include "libbluechi/common/common.h"
15
#include "libbluechi/common/event-util.h"
16
#include "libbluechi/common/network.h"
17
#include "libbluechi/common/opt.h"
18
#include "libbluechi/common/parse-util.h"
19
#include "libbluechi/common/time-util.h"
20
#include "libbluechi/log/log.h"
21
#include "libbluechi/service/shutdown.h"
22

23
#include "agent.h"
24
#include "proxy.h"
25

26
#ifdef USE_USER_API_BUS
27
#        define ALWAYS_USER_API_BUS 1
28
#else
29
#        define ALWAYS_USER_API_BUS 0
30
#endif
31

32
#define DEBUG_SYSTEMD_MESSAGES 0
33
#define DEBUG_SYSTEMD_MESSAGES_CONTENT 0
34

35

36
typedef struct AgentUnitInfoKey {
37
        char *object_path;
38
} AgentUnitInfoKey;
39

40
struct JobTracker {
41
        char *job_object_path;
42
        job_tracker_callback callback;
43
        void *userdata;
44
        free_func_t free_userdata;
45
        LIST_FIELDS(JobTracker, tracked_jobs);
46
};
47

48
static bool
49
                agent_track_job(Agent *agent,
50
                                const char *job_object_path,
51
                                job_tracker_callback callback,
52
                                void *userdata,
53
                                free_func_t free_userdata);
54

55
/* Keep track of outstanding systemd job and connect it back to
56
   the originating bluechi job id so we can proxy changes to it. */
57
typedef struct {
58
        int ref_count;
59

60
        Agent *agent;
61
        uint32_t bc_job_id;
62
        uint64_t job_start_micros;
63
        char *unit;
64
        char *method;
65
} AgentJobOp;
66

67
static AgentJobOp *agent_job_op_ref(AgentJobOp *op) {
133✔
68
        op->ref_count++;
133✔
69
        return op;
133✔
70
}
71

72
static void agent_job_op_unref(AgentJobOp *op) {
196✔
73
        op->ref_count--;
196✔
74
        if (op->ref_count != 0) {
196✔
75
                return;
76
        }
77

78
        agent_unref(op->agent);
63✔
79
        free_and_null(op->unit);
63✔
80
        free_and_null(op->method);
63✔
81
        free(op);
63✔
82
}
83

84
DEFINE_CLEANUP_FUNC(AgentJobOp, agent_job_op_unref)
67✔
85
#define _cleanup_agent_job_op_ _cleanup_(agent_job_op_unrefp)
86

87
static AgentJobOp *agent_job_new(Agent *agent, uint32_t bc_job_id, const char *unit, const char *method) {
67✔
88
        AgentJobOp *op = malloc0(sizeof(AgentJobOp));
67✔
89
        if (op) {
67✔
90
                op->ref_count = 1;
67✔
91
                op->agent = agent_ref(agent);
67✔
92
                op->bc_job_id = bc_job_id;
67✔
93
                op->job_start_micros = 0;
67✔
94
                op->unit = strdup(unit);
67✔
95
                op->method = strdup(method);
67✔
96
        }
97
        return op;
67✔
98
}
99

100
bool agent_is_connected(Agent *agent) {
348✔
101
        return agent != NULL && agent->connection_state == AGENT_CONNECTION_STATE_CONNECTED;
348✔
102
}
103

104
char *agent_is_online(Agent *agent) {
324✔
105
        if (agent_is_connected(agent)) {
324✔
106
                return "online";
157✔
107
        }
108
        return "offline";
109
}
110

111
static int agent_stop_local_proxy_service(Agent *agent, ProxyService *proxy);
112
static bool agent_connect(Agent *agent);
113
static bool agent_reconnect(Agent *agent);
114
static void agent_peer_bus_close(Agent *agent);
115

116
static int agent_disconnected(UNUSED sd_bus_message *message, void *userdata, UNUSED sd_bus_error *error) {
17✔
117
        Agent *agent = (Agent *) userdata;
17✔
118

119
        bc_log_error("Disconnected from controller");
17✔
120

121
        agent->connection_state = AGENT_CONNECTION_STATE_RETRY;
17✔
122
        int r = sd_bus_emit_properties_changed(
17✔
123
                        agent->api_bus, BC_AGENT_OBJECT_PATH, AGENT_INTERFACE, "Status", NULL);
124
        if (r < 0) {
17✔
UNCOV
125
                bc_log_errorf("Failed to emit status property changed: %s", strerror(-r));
×
126
        }
127

128
        agent->disconnect_timestamp = get_time_micros();
17✔
129
        agent->disconnect_timestamp_monotonic = get_time_micros_monotonic();
17✔
130

131
        /* try to reconnect right away */
132
        agent_reconnect(agent);
17✔
133

134
        return 0;
17✔
135
}
136

137
static int agent_reset_heartbeat_timer(Agent *agent, sd_event_source **event_source);
138

139
static bool agent_check_controller_liveness(Agent *agent) {
37✔
140
        uint64_t diff = 0;
37✔
141
        uint64_t now = 0;
37✔
142

143
        if (agent->controller_heartbeat_threshold_msec <= 0) {
37✔
144
                /* checking liveness by heartbeat disabled since configured threshold is <=0 */
145
                return true;
146
        }
147

148
        now = get_time_micros_monotonic();
3✔
149
        if (now == USEC_INFINITY) {
3✔
UNCOV
150
                bc_log_error("Failed to get the monotonic time");
×
UNCOV
151
                return true;
×
152
        }
153

154
        if (now < agent->controller_last_seen_monotonic) {
3✔
UNCOV
155
                bc_log_error("Clock skew detected");
×
UNCOV
156
                return true;
×
157
        }
158

159
        diff = now - agent->controller_last_seen_monotonic;
3✔
160
        if (diff > (uint64_t) agent->controller_heartbeat_threshold_msec * USEC_PER_MSEC) {
3✔
161
                bc_log_infof("Did not receive heartbeat from controller since '%d'ms. Disconnecting it...",
1✔
162
                             agent->controller_heartbeat_threshold_msec);
163
                agent_disconnected(NULL, agent, NULL);
1✔
164
                return false;
1✔
165
        }
166

167
        return true;
168
}
169

170
static int agent_heartbeat_timer_callback(sd_event_source *event_source, UNUSED uint64_t usec, void *userdata) {
44✔
171
        Agent *agent = (Agent *) userdata;
44✔
172

173
        int r = 0;
44✔
174
        /* Being in CONNECTING state when the timer callback is executed implies that
175
         * the agent hasn't received a reply from the controller yet. In this case,
176
         * we drop the existing register call and the agent is set into RETRY state.*/
177
        if (agent->connection_state == AGENT_CONNECTION_STATE_CONNECTING) {
44✔
178
                bc_log_error("Agent connection attempt failed, retrying");
7✔
179
                if (agent->register_call_slot != NULL) {
7✔
180
                        sd_bus_slot_unrefp(&agent->register_call_slot);
7✔
181
                        agent->register_call_slot = NULL;
7✔
182
                }
183
                agent->connection_state = AGENT_CONNECTION_STATE_RETRY;
7✔
184
        }
185
        if (agent->connection_state == AGENT_CONNECTION_STATE_CONNECTED &&
81✔
186
            agent_check_controller_liveness(agent)) {
37✔
187
                r = sd_bus_emit_signal(
36✔
188
                                agent->peer_dbus,
189
                                INTERNAL_AGENT_OBJECT_PATH,
190
                                INTERNAL_AGENT_INTERFACE,
191
                                "Heartbeat",
192
                                "");
193
                if (r < 0) {
36✔
UNCOV
194
                        bc_log_errorf("Failed to emit heartbeat signal: %s", strerror(-r));
×
195
                }
196
        } else if (agent->connection_state == AGENT_CONNECTION_STATE_RETRY) {
8✔
197
                agent->connection_retry_count++;
7✔
198

199
                /* Disable logging to not spam logs in retry loop */
200
                if (agent->connection_retry_count == agent->connection_retry_count_until_quiet) {
7✔
UNCOV
201
                        bc_log_infof("Quieting down logs after connection retry failed %dx...",
×
202
                                     agent->connection_retry_count);
UNCOV
203
                        bc_log_set_quiet(true);
×
204
                }
205
                bc_log_infof("Trying to connect to controller (try %d)", agent->connection_retry_count);
7✔
206
                if (!agent_reconnect(agent)) {
7✔
UNCOV
207
                        bc_log_debugf("Connection retry %d failed", agent->connection_retry_count);
×
208
                }
209
        }
210

211
        r = agent_reset_heartbeat_timer(agent, &event_source);
44✔
212
        if (r < 0) {
44✔
UNCOV
213
                bc_log_errorf("Failed to reset agent heartbeat timer: %s", strerror(-r));
×
UNCOV
214
                return r;
×
215
        }
216

217
        return 0;
218
}
219

220
static int agent_reset_heartbeat_timer(Agent *agent, sd_event_source **event_source) {
192✔
221
        return event_reset_time_relative(
384✔
222
                        agent->event,
223
                        event_source,
224
                        CLOCK_BOOTTIME,
225
                        agent->heartbeat_interval_msec * USEC_PER_MSEC,
192✔
226
                        0,
227
                        agent_heartbeat_timer_callback,
228
                        agent,
229
                        0,
230
                        "agent-heartbeat-timer-source",
231
                        false);
232
}
233

234
static int agent_setup_heartbeat_timer(Agent *agent) {
150✔
235
        _cleanup_(sd_event_source_unrefp) sd_event_source *event_source = NULL;
150✔
236
        int r = 0;
150✔
237

238
        assert(agent);
150✔
239

240
        if (agent->heartbeat_interval_msec <= 0) {
150✔
241
                bc_log_warnf("Heartbeat disabled since configured interval '%d' is <=0",
2✔
242
                             agent->heartbeat_interval_msec);
243
                return 0;
244
        }
245

246
        r = agent_reset_heartbeat_timer(agent, &event_source);
148✔
247
        if (r < 0) {
148✔
UNCOV
248
                bc_log_errorf("Failed to reset agent heartbeat timer: %s", strerror(-r));
×
249
                return r;
250
        }
251

252
        return sd_event_source_set_floating(event_source, true);
148✔
253
}
254

255
SystemdRequest *systemd_request_ref(SystemdRequest *req) {
150✔
256
        req->ref_count++;
150✔
257
        return req;
150✔
258
}
259

260
void systemd_request_unref(SystemdRequest *req) {
297✔
261
        Agent *agent = req->agent;
297✔
262

263
        req->ref_count--;
297✔
264
        if (req->ref_count != 0) {
297✔
265
                return;
266
        }
267

268
        if (req->userdata && req->free_userdata) {
147✔
269
                req->free_userdata(req->userdata);
66✔
270
        }
271

272
        sd_bus_message_unrefp(&req->request_message);
147✔
273
        sd_bus_slot_unrefp(&req->slot);
147✔
274
        sd_bus_message_unrefp(&req->message);
147✔
275

276
        LIST_REMOVE(outstanding_requests, agent->outstanding_requests, req);
147✔
277
        agent_unref(req->agent);
147✔
278
        free(req);
147✔
279
}
280

281
static SystemdRequest *agent_create_request_full(
150✔
282
                Agent *agent,
283
                sd_bus_message *request_message,
284
                const char *object_path,
285
                const char *iface,
286
                const char *method) {
287
        _cleanup_systemd_request_ SystemdRequest *req = malloc0(sizeof(SystemdRequest));
150✔
288
        if (req == NULL) {
150✔
289
                return NULL;
290
        }
291

292
        req->ref_count = 1;
150✔
293
        req->agent = agent_ref(agent);
150✔
294
        LIST_INIT(outstanding_requests, req);
150✔
295
        req->request_message = sd_bus_message_ref(request_message);
150✔
296

297
        LIST_APPEND(outstanding_requests, agent->outstanding_requests, req);
150✔
298

299
        int r = sd_bus_message_new_method_call(
150✔
300
                        agent->systemd_dbus, &req->message, SYSTEMD_BUS_NAME, object_path, iface, method);
301
        if (r < 0) {
150✔
UNCOV
302
                bc_log_errorf("Failed to create new bus message: %s", strerror(-r));
×
303
                return NULL;
304
        }
305

306
        return steal_pointer(&req);
307
}
308

309
static SystemdRequest *agent_create_request(Agent *agent, sd_bus_message *request_message, const char *method) {
128✔
310
        return agent_create_request_full(
128✔
311
                        agent, request_message, SYSTEMD_OBJECT_PATH, SYSTEMD_MANAGER_IFACE, method);
312
}
313

314
static void systemd_request_set_userdata(SystemdRequest *req, void *userdata, free_func_t free_userdata) {
67✔
315
        req->userdata = userdata;
67✔
316
        req->free_userdata = free_userdata;
67✔
317
}
67✔
318

319
static bool systemd_request_start(SystemdRequest *req, sd_bus_message_handler_t callback) {
150✔
320
        Agent *agent = req->agent;
150✔
321
        AgentJobOp *op = req->userdata;
150✔
322
        if (op != NULL) {
150✔
323
                op->job_start_micros = get_time_micros();
67✔
324
        }
325

326
        int r = sd_bus_call_async(
150✔
327
                        agent->systemd_dbus, &req->slot, req->message, callback, req, BC_DEFAULT_DBUS_TIMEOUT);
328
        if (r < 0) {
150✔
UNCOV
329
                bc_log_errorf("Failed to call async: %s", strerror(-r));
×
UNCOV
330
                return false;
×
331
        }
332

333
        systemd_request_ref(req); /* outstanding callback owns this ref */
150✔
334
        return true;
150✔
335
}
336

337
static char *make_unit_path(const char *unit) {
26,388✔
338
        _cleanup_free_ char *escaped = bus_path_escape(unit);
26,388✔
339

340
        if (escaped == NULL) {
26,388✔
341
                return NULL;
342
        }
343

344
        return strcat_dup(SYSTEMD_OBJECT_PATH "/unit/", escaped);
26,388✔
345
}
346

347
static void unit_info_clear(void *item) {
25,646✔
348
        AgentUnitInfo *info = item;
25,646✔
349
        free_and_null(info->object_path);
25,646✔
350
        free_and_null(info->unit);
25,646✔
351
        free_and_null(info->substate);
25,646✔
352
}
25,646✔
353

354
static uint64_t unit_info_hash(const void *item, uint64_t seed0, uint64_t seed1) {
99,728✔
355
        const AgentUnitInfo *info = item;
99,728✔
356
        return hashmap_sip(info->object_path, strlen(info->object_path), seed0, seed1);
99,728✔
357
}
358

359
static int unit_info_compare(const void *a, const void *b, UNUSED void *udata) {
47,054✔
360
        const AgentUnitInfo *info_a = a;
47,054✔
361
        const AgentUnitInfo *info_b = b;
47,054✔
362

363
        return strcmp(info_a->object_path, info_b->object_path);
47,054✔
364
}
365

366
static const char *unit_info_get_substate(AgentUnitInfo *info) {
3,082✔
367
        return info->substate ? info->substate : "invalid";
3,082✔
368
}
369

370
struct UpdateStateData {
371
        UnitActiveState active_state;
372
        const char *substate;
373
};
374

375

376
static int unit_info_update_state_cb(const char *key, const char *value_type, sd_bus_message *m, void *userdata) {
32,780✔
377
        struct UpdateStateData *data = userdata;
32,780✔
378
        const char *value = NULL;
32,780✔
379

380
        if (!streq(key, "ActiveState") && !streq(key, "SubState") && !streq(value_type, "s")) {
32,780✔
381
                return 2; /* skip */
32,780✔
382
        }
383

384
        int r = sd_bus_message_enter_container(m, SD_BUS_TYPE_VARIANT, value_type);
4,470✔
385
        if (r < 0) {
4,470✔
386
                return r;
387
        }
388

389
        r = sd_bus_message_read_basic(m, SD_BUS_TYPE_STRING, &value);
4,470✔
390
        if (r < 0) {
4,470✔
391
                return r;
392
        }
393

394
        if (streq(key, "ActiveState")) {
4,470✔
395
                data->active_state = active_state_from_string(value);
1,490✔
396
        } else if (streq(key, "SubState")) {
2,980✔
397
                data->substate = value;
1,490✔
398
        }
399

400
        r = sd_bus_message_exit_container(m);
4,470✔
401
        if (r < 0) {
4,470✔
UNCOV
402
                return r;
×
403
        }
404

405
        return 0;
406
}
407

408

409
/* Updates stored unit state from property change, returns true if the state changes */
410
static bool unit_info_update_state(AgentUnitInfo *info, sd_bus_message *m) {
1,490✔
411
        struct UpdateStateData data = {
1,490✔
412
                info->active_state,
1,490✔
413
                unit_info_get_substate(info),
1,490✔
414
        };
415
        int r = bus_parse_properties_foreach(m, unit_info_update_state_cb, &data);
1,490✔
416
        if (r < 0) {
1,490✔
417
                return false;
1,490✔
418
        }
419

420
        bool changed = false;
1,490✔
421
        if (data.active_state != info->active_state) {
1,490✔
422
                info->active_state = data.active_state;
481✔
423
                changed = true;
481✔
424
        }
425
        if (!streq(data.substate, unit_info_get_substate(info))) {
1,490✔
426
                free(info->substate);
515✔
427
                info->substate = strdup(data.substate);
515✔
428
                changed = true;
515✔
429
        }
430

431
        return changed;
432
}
433

434
Agent *agent_new(void) {
175✔
435
        int r = 0;
175✔
436
        _cleanup_sd_event_ sd_event *event = NULL;
175✔
437
        r = sd_event_default(&event);
175✔
438
        if (r < 0) {
175✔
UNCOV
439
                bc_log_errorf("Failed to create event loop: %s", strerror(-r));
×
440
                return NULL;
441
        }
442

443
        _cleanup_free_ char *service_name = strdup(BC_AGENT_DBUS_NAME);
175✔
444
        if (service_name == NULL) {
175✔
UNCOV
445
                bc_log_error("Out of memory");
×
446
                return NULL;
447
        }
448

449
        _cleanup_free_ SocketOptions *socket_opts = socket_options_new();
350✔
450
        if (socket_opts == NULL) {
175✔
UNCOV
451
                bc_log_error("Out of memory");
×
452
                return NULL;
453
        }
454

455
        struct hashmap *unit_infos = hashmap_new(
175✔
456
                        sizeof(AgentUnitInfo), 0, 0, 0, unit_info_hash, unit_info_compare, unit_info_clear, NULL);
457
        if (unit_infos == NULL) {
175✔
458
                return NULL;
459
        }
460

461
        _cleanup_agent_ Agent *agent = malloc0(sizeof(Agent));
175✔
462
        agent->ref_count = 1;
175✔
463
        agent->event = steal_pointer(&event);
175✔
464
        agent->api_bus_service_name = steal_pointer(&service_name);
175✔
465
        agent->peer_socket_options = steal_pointer(&socket_opts);
175✔
466
        agent->unit_infos = unit_infos;
175✔
467
        agent->connection_state = AGENT_CONNECTION_STATE_DISCONNECTED;
175✔
468
        agent->connection_retry_count = 0;
175✔
469
        agent->controller_last_seen = 0;
175✔
470
        agent->controller_last_seen_monotonic = 0;
175✔
471
        agent->wildcard_subscription_active = false;
175✔
472
        agent->metrics_enabled = false;
175✔
473
        agent->disconnect_timestamp = 0;
175✔
474
        agent->disconnect_timestamp_monotonic = 0;
175✔
475
        agent->connection_retry_count_until_quiet = 0;
175✔
476
        LIST_HEAD_INIT(agent->outstanding_requests);
175✔
477
        LIST_HEAD_INIT(agent->tracked_jobs);
175✔
478
        LIST_HEAD_INIT(agent->proxy_services);
175✔
479

480
        return steal_pointer(&agent);
175✔
481
}
482

483
Agent *agent_ref(Agent *agent) {
217✔
484
        assert(agent->ref_count > 0);
217✔
485

486
        agent->ref_count++;
217✔
487
        return agent;
217✔
488
}
489

490
/* Emit == false on agent shutdown or proxy creation error */
491
void agent_remove_proxy(Agent *agent, ProxyService *proxy, bool emit_removed) {
13✔
492
        assert(proxy->agent == agent);
13✔
493

494
        bc_log_debugf("Removing proxy %s %s", proxy->node_name, proxy->unit_name);
13✔
495

496
        if (emit_removed) {
13✔
497
                int r = proxy_service_emit_proxy_removed(proxy);
11✔
498
                if (r < 0) {
11✔
499
                        bc_log_errorf("Failed to emit ProxyRemoved signal for node %s and unit %s",
×
500
                                      proxy->node_name,
501
                                      proxy->unit_name);
502
                }
503
        }
504

505
        if (proxy->request_message != NULL) {
13✔
UNCOV
506
                int r = sd_bus_reply_method_errorf(
×
507
                                proxy->request_message, SD_BUS_ERROR_FAILED, "Proxy service stopped");
UNCOV
508
                if (r < 0) {
×
UNCOV
509
                        bc_log_errorf("Failed to sent ready status message to proxy: %s", strerror(-r));
×
510
                }
UNCOV
511
                sd_bus_message_unrefp(&proxy->request_message);
×
UNCOV
512
                proxy->request_message = NULL;
×
513
        }
514

515

516
        if (proxy->sent_successful_ready) {
13✔
517
                int r = agent_stop_local_proxy_service(agent, proxy);
10✔
518
                if (r < 0) {
10✔
UNCOV
519
                        bc_log_errorf("Failed to stop proxy service: %s", strerror(-r));
×
520
                }
521
        }
522

523
        proxy_service_unexport(proxy);
13✔
524

525
        LIST_REMOVE(proxy_services, agent->proxy_services, proxy);
13✔
526
        proxy->agent = NULL;
13✔
527
        proxy_service_unref(proxy);
13✔
528
}
13✔
529

530
void agent_unref(Agent *agent) {
385✔
531
        assert(agent->ref_count > 0);
385✔
532

533
        agent->ref_count--;
385✔
534
        if (agent->ref_count != 0) {
385✔
535
                return;
536
        }
537

538
        bc_log_debug("Finalizing agent");
169✔
539

540
        /* These are removed in agent_stop */
541
        assert(LIST_IS_EMPTY(agent->proxy_services));
169✔
542

543
        hashmap_free(agent->unit_infos);
169✔
544

545
        free_and_null(agent->name);
169✔
546
        free_and_null(agent->host);
169✔
547
        free_and_null(agent->assembled_controller_address);
169✔
548
        free_and_null(agent->api_bus_service_name);
169✔
549
        free_and_null(agent->controller_address);
169✔
550
        free_and_null(agent->peer_socket_options);
169✔
551

552
        if (agent->event != NULL) {
169✔
553
                sd_event_unrefp(&agent->event);
169✔
554
        }
555

556
        if (agent->register_call_slot != NULL) {
169✔
UNCOV
557
                sd_bus_slot_unrefp(&agent->register_call_slot);
×
558
        }
559
        if (agent->metrics_slot != NULL) {
169✔
560
                sd_bus_slot_unrefp(&agent->metrics_slot);
2✔
561
        }
562

563
        if (agent->peer_dbus != NULL) {
169✔
UNCOV
564
                sd_bus_unrefp(&agent->peer_dbus);
×
565
        }
566
        if (agent->api_bus != NULL) {
169✔
567
                sd_bus_unrefp(&agent->api_bus);
144✔
568
        }
569
        if (agent->systemd_dbus != NULL) {
169✔
570
                sd_bus_unrefp(&agent->systemd_dbus);
144✔
571
        }
572

573
        if (agent->config) {
169✔
574
                cfg_dispose(agent->config);
169✔
575
                agent->config = NULL;
169✔
576
        }
577
        free(agent);
169✔
578
}
579

580
bool agent_set_port(Agent *agent, const char *port_s) {
154✔
581
        uint16_t port = 0;
154✔
582

583
        if (!parse_port(port_s, &port)) {
154✔
584
                bc_log_errorf("Invalid port format '%s'", port_s);
5✔
585
                return false;
5✔
586
        }
587
        agent->port = port;
149✔
588
        return true;
149✔
589
}
590

591
bool agent_set_controller_address(Agent *agent, const char *address) {
8✔
592
        return copy_str(&agent->controller_address, address);
8✔
593
}
594

595
bool agent_set_assembled_controller_address(Agent *agent, const char *address) {
175✔
596
        return copy_str(&agent->assembled_controller_address, address);
175✔
597
}
598

599
bool agent_set_host(Agent *agent, const char *host) {
149✔
600
        return copy_str(&agent->host, host);
149✔
601
}
602

603
bool agent_set_name(Agent *agent, const char *name) {
156✔
604
        return copy_str(&agent->name, name);
156✔
605
}
606

607
bool agent_set_heartbeat_interval(Agent *agent, const char *interval_msec) {
151✔
608
        long interval = 0;
151✔
609

610
        if (!parse_long(interval_msec, &interval)) {
151✔
UNCOV
611
                bc_log_errorf("Invalid heartbeat interval format '%s'", interval_msec);
×
UNCOV
612
                return false;
×
613
        }
614
        agent->heartbeat_interval_msec = interval;
151✔
615
        return true;
151✔
616
}
617

618
bool agent_set_controller_heartbeat_threshold(Agent *agent, const char *threshold_msec) {
150✔
619
        long threshold = 0;
150✔
620

621
        if (!parse_long(threshold_msec, &threshold)) {
150✔
UNCOV
622
                bc_log_errorf("Invalid heartbeat threshold format '%s'", threshold_msec);
×
UNCOV
623
                return false;
×
624
        }
625
        agent->controller_heartbeat_threshold_msec = threshold;
150✔
626
        return true;
150✔
627
}
628

629
void agent_set_systemd_user(Agent *agent, bool systemd_user) {
150✔
630
        agent->systemd_user = systemd_user;
150✔
631
}
150✔
632

633
bool agent_set_connection_retry_count_until_quiet(Agent *agent, const char *retry_count_s) {
150✔
634
        long retry_count = 0;
150✔
635

636
        if (!parse_long(retry_count_s, &retry_count)) {
150✔
NEW
637
                bc_log_errorf("Invalid retry count until quiet format '%s'", retry_count_s);
×
NEW
638
                return false;
×
639
        }
640
        agent->connection_retry_count_until_quiet = retry_count;
150✔
641
        return true;
150✔
642
}
643

644
bool agent_parse_config(Agent *agent, const char *configfile) {
175✔
645
        int result = 0;
175✔
646

647
        result = cfg_initialize(&agent->config);
175✔
648
        if (result != 0) {
175✔
UNCOV
649
                fprintf(stderr, "Error initializing configuration: '%s'.\n", strerror(-result));
×
UNCOV
650
                return false;
×
651
        }
652

653
        result = cfg_agent_def_conf(agent->config);
175✔
654
        if (result < 0) {
175✔
UNCOV
655
                fprintf(stderr, "Failed to set default settings for agent: %s", strerror(-result));
×
UNCOV
656
                return false;
×
657
        }
658
        result = cfg_load_complete_configuration(
175✔
659
                        agent->config,
660
                        CFG_AGENT_DEFAULT_CONFIG,
661
                        CFG_ETC_BC_AGENT_CONF,
662
                        CFG_ETC_AGENT_CONF_DIR,
663
                        configfile);
664
        if (result != 0) {
175✔
665
                return false;
20✔
666
        }
667
        return true;
668
}
669

670
bool agent_apply_config(Agent *agent) {
155✔
671
        const char *value = NULL;
155✔
672
        value = cfg_get_value(agent->config, CFG_NODE_NAME);
155✔
673
        if (value) {
155✔
674
                if (!agent_set_name(agent, value)) {
155✔
675
                        bc_log_error("Failed to set CONTROLLER NAME");
×
UNCOV
676
                        return false;
×
677
                }
678
        }
679

680
        value = cfg_get_value(agent->config, CFG_CONTROLLER_HOST);
155✔
681
        if (value) {
155✔
682
                if (!agent_set_host(agent, value)) {
148✔
UNCOV
683
                        bc_log_error("Failed to set CONTROLLER HOST");
×
UNCOV
684
                        return false;
×
685
                }
686
        }
687

688
        value = cfg_get_value(agent->config, CFG_CONTROLLER_PORT);
155✔
689
        if (value) {
155✔
690
                if (!agent_set_port(agent, value)) {
152✔
691
                        return false;
692
                }
693
        }
694

695
        value = cfg_get_value(agent->config, CFG_CONTROLLER_ADDRESS);
150✔
696
        if (value) {
150✔
697
                if (!agent_set_controller_address(agent, value)) {
7✔
UNCOV
698
                        bc_log_error("Failed to set CONTROLLER ADDRESS");
×
UNCOV
699
                        return false;
×
700
                }
701
        }
702

703
        value = cfg_get_value(agent->config, CFG_HEARTBEAT_INTERVAL);
150✔
704
        if (value) {
150✔
705
                if (!agent_set_heartbeat_interval(agent, value)) {
150✔
706
                        return false;
707
                }
708
        }
709

710
        value = cfg_get_value(agent->config, CFG_CONTROLLER_HEARTBEAT_THRESHOLD);
150✔
711
        if (value) {
150✔
712
                if (!agent_set_controller_heartbeat_threshold(agent, value)) {
150✔
713
                        return false;
714
                }
715
        }
716

717
        value = cfg_get_value(agent->config, CFG_CONNECTION_RETRY_COUNT_UNTIL_QUIET);
150✔
718
        if (value) {
150✔
719
                if (!agent_set_connection_retry_count_until_quiet(agent, value)) {
150✔
720
                        return false;
721
                }
722
        }
723

724
        /* Set socket options used for peer connections with the agents */
725
        const char *keepidle = cfg_get_value(agent->config, CFG_TCP_KEEPALIVE_TIME);
150✔
726
        if (keepidle) {
150✔
727
                if (socket_options_set_tcp_keepidle(agent->peer_socket_options, keepidle) < 0) {
150✔
UNCOV
728
                        bc_log_error("Failed to set TCP KEEPIDLE");
×
UNCOV
729
                        return false;
×
730
                }
731
        }
732
        const char *keepintvl = cfg_get_value(agent->config, CFG_TCP_KEEPALIVE_INTERVAL);
150✔
733
        if (keepintvl) {
150✔
734
                if (socket_options_set_tcp_keepintvl(agent->peer_socket_options, keepintvl) < 0) {
150✔
UNCOV
735
                        bc_log_error("Failed to set TCP KEEPINTVL");
×
UNCOV
736
                        return false;
×
737
                }
738
        }
739
        const char *keepcnt = cfg_get_value(agent->config, CFG_TCP_KEEPALIVE_COUNT);
150✔
740
        if (keepcnt) {
150✔
741
                if (socket_options_set_tcp_keepcnt(agent->peer_socket_options, keepcnt) < 0) {
150✔
UNCOV
742
                        bc_log_error("Failed to set TCP KEEPCNT");
×
UNCOV
743
                        return false;
×
744
                }
745
        }
746
        if (socket_options_set_ip_recverr(
150✔
747
                            agent->peer_socket_options,
748
                            cfg_get_bool_value(agent->config, CFG_IP_RECEIVE_ERRORS)) < 0) {
150✔
UNCOV
749
                bc_log_error("Failed to set IP RECVERR");
×
UNCOV
750
                return false;
×
751
        }
752

753
        return true;
754
}
755

756
static void agent_update_unit_infos_for(Agent *agent, AgentUnitInfo *info) {
9,202✔
757
        if (!info->subscribed && !info->loaded) {
9,202✔
758
                AgentUnitInfoKey key = { info->object_path };
9,175✔
759
                AgentUnitInfo *info = (AgentUnitInfo *) hashmap_delete(agent->unit_infos, &key);
9,175✔
760
                if (info != NULL) {
9,175✔
761
                        unit_info_clear(info);
9,175✔
762
                }
763
        }
764
}
9,202✔
765

766
static AgentUnitInfo *agent_get_unit_info(Agent *agent, const char *unit_path) {
64,216✔
767
        AgentUnitInfoKey key = { (char *) unit_path };
64,216✔
768

769
        return (AgentUnitInfo *) hashmap_get(agent->unit_infos, &key);
64,216✔
770
}
771

772
static AgentUnitInfo *agent_ensure_unit_info(Agent *agent, const char *unit) {
26,364✔
773
        _cleanup_free_ char *unit_path = make_unit_path(unit);
52,728✔
774
        AgentUnitInfo *info = agent_get_unit_info(agent, unit_path);
26,364✔
775
        if (info != NULL) {
26,364✔
776
                return info;
777
        }
778

779
        _cleanup_free_ char *unit_copy = strdup(unit);
26,364✔
780
        if (unit_copy == NULL) {
26,337✔
781
                return NULL;
782
        }
783

784
        AgentUnitInfo v = { unit_path, unit_copy, false, false, -1, NULL };
26,337✔
785

786
        AgentUnitInfo *replaced = (AgentUnitInfo *) hashmap_set(agent->unit_infos, &v);
26,337✔
787
        if (replaced == NULL && hashmap_oom(agent->unit_infos)) {
26,337✔
788
                return NULL;
789
        }
790

791
        info = agent_get_unit_info(agent, unit_path);
26,337✔
792

793
        /* These are now in hashtable */
794
        steal_pointer(&unit_copy);
795
        steal_pointer(&unit_path);
796

797
        return info;
798
}
799

800
static int agent_method_passthrough_to_systemd_cb(
25✔
801
                sd_bus_message *m, void *userdata, UNUSED sd_bus_error *ret_error) {
802
        _cleanup_systemd_request_ SystemdRequest *req = userdata;
25✔
803

804
        if (sd_bus_message_is_method_error(m, NULL)) {
25✔
805
                return sd_bus_reply_method_error(req->request_message, sd_bus_message_get_error(m));
2✔
806
        }
807

808
        _cleanup_sd_bus_message_ sd_bus_message *reply = NULL;
25✔
809
        int r = sd_bus_message_new_method_return(req->request_message, &reply);
23✔
810
        if (r < 0) {
23✔
811
                return r;
812
        }
813

814
        r = sd_bus_message_copy(reply, m, true);
23✔
815
        if (r < 0) {
23✔
816
                return r;
817
        }
818

819
        return sd_bus_message_send(reply);
23✔
820
}
821

822
static int agent_method_passthrough_to_systemd(sd_bus_message *m, void *userdata, UNUSED sd_bus_error *ret_error) {
25✔
823
        Agent *agent = userdata;
25✔
824
        _cleanup_systemd_request_ SystemdRequest *req = agent_create_request(
50✔
825
                        agent, m, sd_bus_message_get_member(m));
826
        if (req == NULL) {
25✔
UNCOV
827
                return sd_bus_reply_method_errorf(m, SD_BUS_ERROR_FAILED, "Failed to create a systemd request");
×
828
        }
829

830
        int r = sd_bus_message_copy(req->message, m, true);
25✔
831
        if (r < 0) {
25✔
UNCOV
832
                return sd_bus_reply_method_errorf(
×
833
                                m, SD_BUS_ERROR_FAILED, "Failed to copy a reply message: %s", strerror(-r));
834
        }
835

836
        if (!systemd_request_start(req, agent_method_passthrough_to_systemd_cb)) {
25✔
UNCOV
837
                return sd_bus_reply_method_errorf(m, SD_BUS_ERROR_FAILED, "Failed to start systemd request");
×
838
        }
839

840
        return 1;
841
}
842

843
/************************************************************************
844
 ********** org.eclipse.bluechi.internal.Agent.ListUnits **
845
 ************************************************************************/
846

847
static int list_units_callback(sd_bus_message *m, void *userdata, UNUSED sd_bus_error *ret_error) {
4✔
848
        _cleanup_systemd_request_ SystemdRequest *req = userdata;
4✔
849

850
        if (sd_bus_message_is_method_error(m, NULL)) {
4✔
851
                /* Forward error */
UNCOV
852
                return sd_bus_reply_method_error(req->request_message, sd_bus_message_get_error(m));
×
853
        }
854

855
        _cleanup_sd_bus_message_ sd_bus_message *reply = NULL;
4✔
856

857
        int r = sd_bus_message_new_method_return(req->request_message, &reply);
4✔
858
        if (r < 0) {
4✔
859
                return r;
860
        }
861

862
        r = sd_bus_message_copy(reply, m, true);
4✔
863
        if (r < 0) {
4✔
864
                return r;
865
        }
866

867
        return sd_bus_message_send(reply);
4✔
868
}
869

870
static int agent_method_list_units(sd_bus_message *m, void *userdata, UNUSED sd_bus_error *ret_error) {
4✔
871
        Agent *agent = userdata;
4✔
872

873
        _cleanup_systemd_request_ SystemdRequest *req = agent_create_request(agent, m, "ListUnits");
8✔
874
        if (req == NULL) {
4✔
UNCOV
875
                return sd_bus_reply_method_errorf(
×
876
                                m,
877
                                SD_BUS_ERROR_FAILED,
878
                                "Failed to create a systemd request for the ListUnits method");
879
        }
880

881
        if (!systemd_request_start(req, list_units_callback)) {
4✔
UNCOV
882
                return sd_bus_reply_method_errorf(m, SD_BUS_ERROR_FAILED, "Failed to start systemd request");
×
883
        }
884

885
        return 1;
886
}
887

888
/************************************************************************
889
 ********** org.eclipse.bluechi.internal.Agent.ListUnitFiles ************
890
 ************************************************************************/
891

892
static int list_unit_files_callback(sd_bus_message *m, void *userdata, UNUSED sd_bus_error *ret_error) {
4✔
893
        _cleanup_systemd_request_ SystemdRequest *req = userdata;
4✔
894

895
        if (sd_bus_message_is_method_error(m, NULL)) {
4✔
UNCOV
896
                return sd_bus_reply_method_error(req->request_message, sd_bus_message_get_error(m));
×
897
        }
898

899
        _cleanup_sd_bus_message_ sd_bus_message *reply = NULL;
4✔
900

901
        int r = sd_bus_message_new_method_return(req->request_message, &reply);
4✔
902
        if (r < 0) {
4✔
903
                return r;
904
        }
905

906
        r = sd_bus_message_copy(reply, m, true);
4✔
907
        if (r < 0) {
4✔
908
                return r;
909
        }
910

911
        return sd_bus_message_send(reply);
4✔
912
}
913

914
static int agent_method_list_unit_files(sd_bus_message *m, void *userdata, UNUSED sd_bus_error *ret_error) {
4✔
915
        Agent *agent = userdata;
4✔
916

917
        _cleanup_systemd_request_ SystemdRequest *req = agent_create_request(agent, m, "ListUnitFiles");
8✔
918
        if (req == NULL) {
4✔
UNCOV
919
                return sd_bus_reply_method_errorf(
×
920
                                m,
921
                                SD_BUS_ERROR_FAILED,
922
                                "Failed to create a systemd request for the ListUnitFiles method");
923
        }
924

925
        if (!systemd_request_start(req, list_unit_files_callback)) {
4✔
UNCOV
926
                return sd_bus_reply_method_errorf(m, SD_BUS_ERROR_FAILED, "Failed to start systemd request");
×
927
        }
928

929
        return 1;
930
}
931

932
/************************************************************************
933
 ******** org.eclipse.bluechi.internal.Agent.GetUnitProperties ************
934
 ************************************************************************/
935

936
static int get_unit_properties_got_properties(sd_bus_message *m, void *userdata, UNUSED sd_bus_error *ret_error) {
7✔
937
        _cleanup_systemd_request_ SystemdRequest *req = userdata;
7✔
938

939
        if (sd_bus_message_is_method_error(m, NULL)) {
7✔
940
                /* Forward error */
UNCOV
941
                return sd_bus_reply_method_error(req->request_message, sd_bus_message_get_error(m));
×
942
        }
943

944
        _cleanup_sd_bus_message_ sd_bus_message *reply = NULL;
7✔
945
        int r = sd_bus_message_new_method_return(req->request_message, &reply);
7✔
946
        if (r < 0) {
7✔
947
                return r;
948
        }
949

950
        r = sd_bus_message_copy(reply, m, true);
7✔
951
        if (r < 0) {
7✔
952
                return r;
953
        }
954

955
        return sd_bus_message_send(reply);
7✔
956
}
957

958
static int agent_method_get_unit_properties(sd_bus_message *m, void *userdata, UNUSED sd_bus_error *ret_error) {
7✔
959
        Agent *agent = userdata;
7✔
960
        const char *interface = NULL;
7✔
961
        const char *unit = NULL;
7✔
962
        _cleanup_free_ char *unit_path = NULL;
7✔
963

964
        int r = sd_bus_message_read(m, "ss", &unit, &interface);
7✔
965
        if (r < 0) {
7✔
966
                return sd_bus_reply_method_errorf(
×
967
                                m,
968
                                SD_BUS_ERROR_FAILED,
969
                                "Failed to read a message containing unit and interface: %s",
970
                                strerror(-r));
971
        }
972

973
        r = assemble_object_path_string(SYSTEMD_OBJECT_PATH "/unit", unit, &unit_path);
7✔
974
        if (r < 0) {
7✔
UNCOV
975
                return sd_bus_reply_method_errorf(m, SD_BUS_ERROR_NO_MEMORY, "OOM when assembling unit path");
×
976
        }
977

978
        _cleanup_systemd_request_ SystemdRequest *req = agent_create_request_full(
14✔
979
                        agent, m, unit_path, "org.freedesktop.DBus.Properties", "GetAll");
980
        if (req == NULL) {
7✔
UNCOV
981
                return sd_bus_reply_method_errorf(
×
982
                                m,
983
                                SD_BUS_ERROR_FAILED,
984
                                "Failed to create a systemd request for the GetAll method");
985
        }
986

987
        r = sd_bus_message_append(req->message, "s", interface);
7✔
988
        if (r < 0) {
7✔
UNCOV
989
                return sd_bus_reply_method_errorf(
×
990
                                m,
991
                                SD_BUS_ERROR_FAILED,
992
                                "Failed to append the interface to the message: %s",
993
                                strerror(-r));
994
        }
995

996
        if (!systemd_request_start(req, get_unit_properties_got_properties)) {
7✔
UNCOV
997
                return sd_bus_reply_method_errorf(m, SD_BUS_ERROR_FAILED, "Failed to start systemd request");
×
998
        }
999

1000
        return 1;
1001
}
1002

1003
/***************************************************************************
1004
 ******** org.eclipse.bluechi.internal.Agent.GetUnitProperty *
1005
 ***************************************************************************/
1006

1007
static int get_unit_property_got_property(sd_bus_message *m, void *userdata, UNUSED sd_bus_error *ret_error) {
11✔
1008
        _cleanup_systemd_request_ SystemdRequest *req = userdata;
11✔
1009

1010
        if (sd_bus_message_is_method_error(m, NULL)) {
11✔
1011
                /* Forward error */
UNCOV
1012
                return sd_bus_reply_method_error(req->request_message, sd_bus_message_get_error(m));
×
1013
        }
1014

1015
        _cleanup_sd_bus_message_ sd_bus_message *reply = NULL;
11✔
1016
        int r = sd_bus_message_new_method_return(req->request_message, &reply);
11✔
1017
        if (r < 0) {
11✔
1018
                return r;
1019
        }
1020

1021
        r = sd_bus_message_copy(reply, m, true);
11✔
1022
        if (r < 0) {
11✔
1023
                return r;
1024
        }
1025

1026
        return sd_bus_message_send(reply);
11✔
1027
}
1028

1029
static int agent_method_get_unit_property(sd_bus_message *m, void *userdata, UNUSED sd_bus_error *ret_error) {
11✔
1030
        Agent *agent = userdata;
11✔
1031
        const char *interface = NULL;
11✔
1032
        const char *unit = NULL;
11✔
1033
        const char *property = NULL;
11✔
1034
        _cleanup_free_ char *unit_path = NULL;
11✔
1035

1036
        int r = sd_bus_message_read(m, "sss", &unit, &interface, &property);
11✔
1037
        if (r < 0) {
11✔
1038
                return sd_bus_reply_method_errorf(
×
1039
                                m,
1040
                                SD_BUS_ERROR_FAILED,
1041
                                "Failed to read a message containing unit, interface, and property: %s",
1042
                                strerror(-r));
1043
        }
1044

1045
        r = assemble_object_path_string(SYSTEMD_OBJECT_PATH "/unit", unit, &unit_path);
11✔
1046
        if (r < 0) {
11✔
UNCOV
1047
                return sd_bus_reply_method_errorf(m, SD_BUS_ERROR_NO_MEMORY, "Out of memory");
×
1048
        }
1049

1050
        _cleanup_systemd_request_ SystemdRequest *req = agent_create_request_full(
22✔
1051
                        agent, m, unit_path, "org.freedesktop.DBus.Properties", "Get");
1052
        if (req == NULL) {
11✔
UNCOV
1053
                return sd_bus_reply_method_errorf(
×
1054
                                m, SD_BUS_ERROR_FAILED, "Failed to create a systemd request for the Get method");
1055
        }
1056

1057
        r = sd_bus_message_append(req->message, "ss", interface, property);
11✔
1058
        if (r < 0) {
11✔
UNCOV
1059
                return sd_bus_reply_method_errorf(
×
1060
                                m,
1061
                                SD_BUS_ERROR_FAILED,
1062
                                "Failed to append interface and property to the message: %s",
1063
                                strerror(-r));
1064
        }
1065

1066
        if (!systemd_request_start(req, get_unit_property_got_property)) {
11✔
UNCOV
1067
                return sd_bus_reply_method_errorf(m, SD_BUS_ERROR_FAILED, "Failed to start systemd request");
×
1068
        }
1069

1070
        return 1;
1071
}
1072

1073
/******************************************************************************
1074
 ******** org.eclipse.bluechi.internal.Agent.SetUnitProperties  *
1075
 ******************************************************************************/
1076

1077
static int set_unit_properties_cb(sd_bus_message *m, void *userdata, UNUSED sd_bus_error *ret_error) {
1✔
1078
        _cleanup_systemd_request_ SystemdRequest *req = userdata;
1✔
1079

1080
        if (sd_bus_message_is_method_error(m, NULL)) {
1✔
1081
                /* Forward error */
UNCOV
1082
                return sd_bus_reply_method_error(req->request_message, sd_bus_message_get_error(m));
×
1083
        }
1084

1085
        _cleanup_sd_bus_message_ sd_bus_message *reply = NULL;
1✔
1086
        int r = sd_bus_message_new_method_return(req->request_message, &reply);
1✔
1087
        if (r < 0) {
1✔
1088
                return r;
1089
        }
1090

1091
        r = sd_bus_message_copy(reply, m, true);
1✔
1092
        if (r < 0) {
1✔
1093
                return r;
1094
        }
1095

1096
        return sd_bus_message_send(reply);
1✔
1097
}
1098

1099
static int agent_method_set_unit_properties(sd_bus_message *m, void *userdata, UNUSED sd_bus_error *ret_error) {
1✔
1100
        Agent *agent = userdata;
1✔
1101
        const char *unit = NULL;
1✔
1102
        int runtime = 0;
1✔
1103
        _cleanup_free_ char *unit_path = NULL;
1✔
1104

1105
        int r = sd_bus_message_read(m, "sb", &unit, &runtime);
1✔
1106
        if (r < 0) {
1✔
1107
                return sd_bus_reply_method_errorf(
×
1108
                                m,
1109
                                SD_BUS_ERROR_FAILED,
1110
                                "Failed to read a message containing unit and runtime: %s",
1111
                                strerror(-r));
1112
        }
1113

1114
        r = assemble_object_path_string(SYSTEMD_OBJECT_PATH "/unit", unit, &unit_path);
1✔
1115
        if (r < 0) {
1✔
UNCOV
1116
                return sd_bus_reply_method_errorf(m, SD_BUS_ERROR_NO_MEMORY, "Out of memory");
×
1117
        }
1118

1119
        _cleanup_systemd_request_ SystemdRequest *req = agent_create_request_full(
2✔
1120
                        agent, m, unit_path, "org.freedesktop.systemd1.Unit", "SetProperties");
1121
        if (req == NULL) {
1✔
UNCOV
1122
                return sd_bus_reply_method_errorf(
×
1123
                                m,
1124
                                SD_BUS_ERROR_FAILED,
1125
                                "Failed to create a systemd request for the SetProperties method");
1126
        }
1127

1128
        r = sd_bus_message_append(req->message, "b", runtime);
1✔
1129
        if (r < 0) {
1✔
1130
                return sd_bus_reply_method_errorf(
×
1131
                                m,
1132
                                SD_BUS_ERROR_FAILED,
1133
                                "Failed to append a runtime to a message: %s",
1134
                                strerror(-r));
1135
        }
1136

1137
        r = sd_bus_message_copy(req->message, m, false);
1✔
1138
        if (r < 0) {
1✔
UNCOV
1139
                return sd_bus_reply_method_errorf(
×
1140
                                m, SD_BUS_ERROR_FAILED, "Failed to copy a message: %s", strerror(-r));
1141
        }
1142

1143
        if (!systemd_request_start(req, set_unit_properties_cb)) {
1✔
UNCOV
1144
                return sd_bus_reply_method_errorf(m, SD_BUS_ERROR_FAILED, "Failed to start a systemd request");
×
1145
        }
1146

1147
        return 1;
1148
}
1149

1150
/******************************************************************************
1151
 ******** org.eclipse.bluechi.internal.Agent.FreezeUnit  *
1152
 ******************************************************************************/
1153

1154
static int freeze_unit_cb(sd_bus_message *m, void *userdata, UNUSED sd_bus_error *ret_error) {
1✔
1155
        _cleanup_systemd_request_ SystemdRequest *req = userdata;
1✔
1156

1157
        if (sd_bus_message_is_method_error(m, NULL)) {
1✔
1158
                /* Forward error */
UNCOV
1159
                return sd_bus_reply_method_error(req->request_message, sd_bus_message_get_error(m));
×
1160
        }
1161

1162
        _cleanup_sd_bus_message_ sd_bus_message *reply = NULL;
1✔
1163
        int r = sd_bus_message_new_method_return(req->request_message, &reply);
1✔
1164
        if (r < 0) {
1✔
1165
                return r;
1166
        }
1167

1168
        r = sd_bus_message_copy(reply, m, true);
1✔
1169
        if (r < 0) {
1✔
1170
                return r;
1171
        }
1172

1173
        return sd_bus_message_send(reply);
1✔
1174
}
1175

1176
static int agent_method_freeze_unit(sd_bus_message *m, void *userdata, UNUSED sd_bus_error *ret_error) {
1✔
1177
        Agent *agent = userdata;
1✔
1178
        const char *unit = NULL;
1✔
1179
        _cleanup_free_ char *unit_path = NULL;
1✔
1180

1181
        int r = sd_bus_message_read(m, "s", &unit);
1✔
1182
        if (r < 0) {
1✔
UNCOV
1183
                return sd_bus_reply_method_errorf(
×
1184
                                m, SD_BUS_ERROR_INVALID_ARGS, "Invalid argument for the unit");
1185
        }
1186

1187
        r = assemble_object_path_string(SYSTEMD_OBJECT_PATH "/unit", unit, &unit_path);
1✔
1188
        if (r < 0) {
1✔
UNCOV
1189
                return sd_bus_reply_method_errorf(m, SD_BUS_ERROR_NO_MEMORY, "Out of memory");
×
1190
        }
1191

1192
        _cleanup_systemd_request_ SystemdRequest *req = agent_create_request_full(
2✔
1193
                        agent, m, unit_path, "org.freedesktop.systemd1.Unit", "Freeze");
1194
        if (req == NULL) {
1✔
UNCOV
1195
                return sd_bus_reply_method_errorf(
×
1196
                                m,
1197
                                SD_BUS_ERROR_FAILED,
1198
                                "Failed to create a systemd request for the Freeze method");
1199
        }
1200

1201
        if (!systemd_request_start(req, freeze_unit_cb)) {
1✔
UNCOV
1202
                return sd_bus_reply_method_errorf(m, SD_BUS_ERROR_FAILED, "Failed to start systemd request");
×
1203
        }
1204

1205
        return 1;
1206
}
1207

1208
/******************************************************************************
1209
 ******** org.eclipse.bluechi.internal.Agent.ThawUnit  *
1210
 ******************************************************************************/
1211

1212
static int thaw_unit_cb(sd_bus_message *m, void *userdata, UNUSED sd_bus_error *ret_error) {
1✔
1213
        _cleanup_systemd_request_ SystemdRequest *req = userdata;
1✔
1214

1215
        if (sd_bus_message_is_method_error(m, NULL)) {
1✔
1216
                /* Forward error */
UNCOV
1217
                return sd_bus_reply_method_error(req->request_message, sd_bus_message_get_error(m));
×
1218
        }
1219

1220
        _cleanup_sd_bus_message_ sd_bus_message *reply = NULL;
1✔
1221
        int r = sd_bus_message_new_method_return(req->request_message, &reply);
1✔
1222
        if (r < 0) {
1✔
1223
                return r;
1224
        }
1225

1226
        r = sd_bus_message_copy(reply, m, true);
1✔
1227
        if (r < 0) {
1✔
1228
                return r;
1229
        }
1230

1231
        return sd_bus_message_send(reply);
1✔
1232
}
1233

1234
static int agent_method_thaw_unit(sd_bus_message *m, void *userdata, UNUSED sd_bus_error *ret_error) {
1✔
1235
        Agent *agent = userdata;
1✔
1236
        const char *unit = NULL;
1✔
1237
        _cleanup_free_ char *unit_path = NULL;
1✔
1238

1239
        int r = sd_bus_message_read(m, "s", &unit);
1✔
1240
        if (r < 0) {
1✔
UNCOV
1241
                return sd_bus_reply_method_errorf(
×
1242
                                m, SD_BUS_ERROR_INVALID_ARGS, "Invalid argument for the unit");
1243
        }
1244

1245
        r = assemble_object_path_string(SYSTEMD_OBJECT_PATH "/unit", unit, &unit_path);
1✔
1246
        if (r < 0) {
1✔
UNCOV
1247
                return sd_bus_reply_method_errorf(m, SD_BUS_ERROR_NO_MEMORY, "Out of memory");
×
1248
        }
1249

1250
        _cleanup_systemd_request_ SystemdRequest *req = agent_create_request_full(
2✔
1251
                        agent, m, unit_path, "org.freedesktop.systemd1.Unit", "Thaw");
1252
        if (req == NULL) {
1✔
UNCOV
1253
                return sd_bus_reply_method_errorf(
×
1254
                                m,
1255
                                SD_BUS_ERROR_FAILED,
1256
                                "Failed to create a systemd request for the Thaw method");
1257
        }
1258

1259
        if (!systemd_request_start(req, thaw_unit_cb)) {
1✔
UNCOV
1260
                return sd_bus_reply_method_errorf(m, SD_BUS_ERROR_FAILED, "Failed to start a systemd request");
×
1261
        }
1262

1263
        return 1;
1264
}
1265

1266
static void agent_job_done(UNUSED sd_bus_message *m, const char *result, void *userdata) {
63✔
1267
        AgentJobOp *op = userdata;
63✔
1268
        Agent *agent = op->agent;
63✔
1269

1270
        if (agent->metrics_enabled) {
63✔
1271
                agent_send_job_metrics(
5✔
1272
                                agent,
1273
                                op->unit,
1274
                                op->method,
1275
                                // NOLINTNEXTLINE(bugprone-narrowing-conversions, cppcoreguidelines-narrowing-conversions)
1276
                                finalize_time_interval_micros(op->job_start_micros));
5✔
1277
        }
1278

1279
        bc_log_infof("Sending JobDone %u, result: %s", op->bc_job_id, result);
63✔
1280

1281
        int r = sd_bus_emit_signal(
63✔
1282
                        agent->peer_dbus,
1283
                        INTERNAL_AGENT_OBJECT_PATH,
1284
                        INTERNAL_AGENT_INTERFACE,
1285
                        "JobDone",
1286
                        "us",
1287
                        op->bc_job_id,
1288
                        result);
1289
        if (r < 0) {
63✔
1290
                bc_log_errorf("Failed to emit JobDone: %s", strerror(-r));
1✔
1291
        }
1292
}
63✔
1293

1294
static int unit_lifecycle_method_callback(sd_bus_message *m, void *userdata, UNUSED sd_bus_error *ret_error) {
66✔
1295
        _cleanup_systemd_request_ SystemdRequest *req = userdata;
66✔
1296
        Agent *agent = req->agent;
66✔
1297
        const char *job_object_path = NULL;
66✔
1298

1299
        if (sd_bus_message_is_method_error(m, NULL)) {
66✔
1300
                /* Forward error */
UNCOV
1301
                return sd_bus_reply_method_error(req->request_message, sd_bus_message_get_error(m));
×
1302
        }
1303

1304
        int r = sd_bus_message_read(m, "o", &job_object_path);
66✔
1305
        if (r < 0) {
66✔
UNCOV
1306
                return sd_bus_reply_method_errorf(
×
1307
                                req->request_message,
1308
                                SD_BUS_ERROR_FAILED,
1309
                                "Failed to read a message containing the object path of the job: %s",
1310
                                strerror(-r));
1311
        }
1312

1313
        AgentJobOp *op = req->userdata;
66✔
1314

1315
        if (!agent_track_job(
66✔
1316
                            agent,
1317
                            job_object_path,
1318
                            agent_job_done,
1319
                            agent_job_op_ref(op),
66✔
1320
                            (free_func_t) agent_job_op_unref)) {
UNCOV
1321
                return sd_bus_reply_method_errorf(
×
1322
                                req->request_message, SD_BUS_ERROR_FAILED, "Failed to track a job");
1323
        }
1324

1325
        return sd_bus_reply_method_return(req->request_message, "");
66✔
1326
}
1327

1328
static int agent_run_unit_lifecycle_method(sd_bus_message *m, Agent *agent, const char *method) {
67✔
1329
        const char *name = NULL;
67✔
1330
        const char *mode = NULL;
67✔
1331
        uint32_t job_id = 0;
67✔
1332

1333
        int r = sd_bus_message_read(m, "ssu", &name, &mode, &job_id);
67✔
1334
        if (r < 0) {
67✔
UNCOV
1335
                return sd_bus_reply_method_errorf(
×
1336
                                m,
1337
                                SD_BUS_ERROR_INVALID_ARGS,
1338
                                "Failed to read a message containing name, mode, and job ID: %s",
1339
                                strerror(-r));
1340
        }
1341

1342
        bc_log_infof("Request to %s unit: %s - Action: %s", method, name, mode);
67✔
1343

1344
        _cleanup_systemd_request_ SystemdRequest *req = agent_create_request(agent, m, method);
134✔
1345
        if (req == NULL) {
67✔
UNCOV
1346
                return sd_bus_reply_method_errorf(
×
1347
                                m,
1348
                                SD_BUS_ERROR_FAILED,
1349
                                "Failed to create a systemd request for %s method: %s",
1350
                                method,
1351
                                strerror(-r));
1352
        }
1353

1354
        _cleanup_agent_job_op_ AgentJobOp *op = agent_job_new(agent, job_id, name, method);
134✔
1355
        if (op == NULL) {
67✔
UNCOV
1356
                return sd_bus_reply_method_errorf(m, SD_BUS_ERROR_FAILED, "Failed to create JobOp");
×
1357
        }
1358

1359
        systemd_request_set_userdata(req, agent_job_op_ref(op), (free_func_t) agent_job_op_unref);
67✔
1360

1361
        r = sd_bus_message_append(req->message, "ss", name, mode);
67✔
1362
        if (r < 0) {
67✔
UNCOV
1363
                return sd_bus_reply_method_errorf(
×
1364
                                m,
1365
                                SD_BUS_ERROR_FAILED,
1366
                                "Failed to append name and mode to the message: %s",
1367
                                strerror(-r));
1368
        }
1369

1370
        if (!systemd_request_start(req, unit_lifecycle_method_callback)) {
67✔
UNCOV
1371
                return sd_bus_reply_method_errorf(m, SD_BUS_ERROR_FAILED, "Failed to start a systemd request");
×
1372
        }
1373

1374
        return 1;
1375
}
1376

1377
/*************************************************************************
1378
 ********** org.eclipse.bluechi.internal.Agent.StartUnit   *
1379
 *************************************************************************/
1380

1381
static int agent_method_start_unit(sd_bus_message *m, void *userdata, UNUSED sd_bus_error *ret_error) {
48✔
1382
        return agent_run_unit_lifecycle_method(m, (Agent *) userdata, "StartUnit");
48✔
1383
}
1384

1385
/*************************************************************************
1386
 ********** org.eclipse.bluechi.internal.Agent.StopUnit    *
1387
 *************************************************************************/
1388

1389
static int agent_method_stop_unit(sd_bus_message *m, void *userdata, UNUSED sd_bus_error *ret_error) {
16✔
1390
        return agent_run_unit_lifecycle_method(m, (Agent *) userdata, "StopUnit");
16✔
1391
}
1392

1393
/*************************************************************************
1394
 ********** org.eclipse.bluechi.internal.Agent.RestartUnit *
1395
 *************************************************************************/
1396

1397
static int agent_method_restart_unit(sd_bus_message *m, void *userdata, UNUSED sd_bus_error *ret_error) {
2✔
1398
        return agent_run_unit_lifecycle_method(m, (Agent *) userdata, "RestartUnit");
2✔
1399
}
1400

1401
/*************************************************************************
1402
 ********** org.eclipse.bluechi.internal.Agent.ReloadUnit  *
1403
 *************************************************************************/
1404

1405
static int agent_method_reload_unit(sd_bus_message *m, void *userdata, UNUSED sd_bus_error *ret_error) {
1✔
1406
        return agent_run_unit_lifecycle_method(m, (Agent *) userdata, "ReloadUnit");
1✔
1407
}
1408

1409
/*************************************************************************
1410
 ********** org.eclipse.bluechi.internal.Agent.Subscribe ***
1411
 *************************************************************************/
1412

1413
static void agent_emit_unit_new(Agent *agent, AgentUnitInfo *info, const char *reason) {
35✔
1414
        bc_log_debugf("Sending UnitNew %s, reason: %s", info->unit, reason);
35✔
1415
        int r = sd_bus_emit_signal(
35✔
1416
                        agent->peer_dbus,
1417
                        INTERNAL_AGENT_OBJECT_PATH,
1418
                        INTERNAL_AGENT_INTERFACE,
1419
                        "UnitNew",
1420
                        "ss",
1421
                        info->unit,
1422
                        reason);
1423
        if (r < 0) {
35✔
UNCOV
1424
                bc_log_warn("Failed to emit UnitNew");
×
1425
        }
1426
}
35✔
1427

1428
static void agent_emit_unit_removed(Agent *agent, AgentUnitInfo *info) {
19✔
1429
        bc_log_debugf("Sending UnitRemoved %s", info->unit);
19✔
1430

1431
        int r = sd_bus_emit_signal(
19✔
1432
                        agent->peer_dbus,
1433
                        INTERNAL_AGENT_OBJECT_PATH,
1434
                        INTERNAL_AGENT_INTERFACE,
1435
                        "UnitRemoved",
1436
                        "s",
1437
                        info->unit);
1438
        if (r < 0) {
19✔
UNCOV
1439
                bc_log_warn("Failed to emit UnitRemoved");
×
1440
        }
1441
}
19✔
1442

1443
static void agent_emit_unit_state_changed(Agent *agent, AgentUnitInfo *info, const char *reason) {
51✔
1444
        bc_log_debugf("Sending UnitStateChanged %s %s(%s) reason: %s",
51✔
1445
                      info->unit,
1446
                      active_state_to_string(info->active_state),
1447
                      unit_info_get_substate(info),
1448
                      reason);
1449
        int r = sd_bus_emit_signal(
51✔
1450
                        agent->peer_dbus,
1451
                        INTERNAL_AGENT_OBJECT_PATH,
1452
                        INTERNAL_AGENT_INTERFACE,
1453
                        "UnitStateChanged",
1454
                        "ssss",
1455
                        info->unit,
1456
                        active_state_to_string(info->active_state),
1457
                        unit_info_get_substate(info),
1458
                        reason);
1459
        if (r < 0) {
51✔
1460
                bc_log_warn("Failed to emit UnitStateChanged");
×
1461
        }
1462
}
51✔
1463

1464
static int agent_method_subscribe(sd_bus_message *m, void *userdata, UNUSED sd_bus_error *ret_error) {
30✔
1465
        Agent *agent = userdata;
30✔
1466
        const char *unit = NULL;
30✔
1467
        int r = sd_bus_message_read(m, "s", &unit);
30✔
1468
        if (r < 0) {
30✔
UNCOV
1469
                return sd_bus_reply_method_errorf(
×
1470
                                m, SD_BUS_ERROR_INVALID_ARGS, "Invalid argument for the unit: %s", strerror(-r));
1471
        }
1472

1473
        if (is_wildcard(unit)) {
30✔
1474
                if (agent->wildcard_subscription_active) {
6✔
1475
                        return sd_bus_reply_method_errorf(
6✔
1476
                                        m, SD_BUS_ERROR_FAILED, "Already wildcard subscribed");
1477
                }
1478
                agent->wildcard_subscription_active = true;
6✔
1479

1480
                AgentUnitInfo info = { NULL, (char *) unit, true, true, _UNIT_ACTIVE_STATE_INVALID, NULL };
6✔
1481
                agent_emit_unit_new(agent, &info, "virtual");
6✔
1482

1483
                return sd_bus_reply_method_return(m, "");
6✔
1484
        }
1485

1486
        AgentUnitInfo *info = agent_ensure_unit_info(agent, unit);
24✔
1487
        if (info == NULL) {
24✔
UNCOV
1488
                return sd_bus_reply_method_errorf(m, SD_BUS_ERROR_FAILED, "Failed to ensure the unit info");
×
1489
        }
1490

1491
        if (info->subscribed) {
24✔
UNCOV
1492
                return sd_bus_reply_method_errorf(m, SD_BUS_ERROR_FAILED, "Already subscribed");
×
1493
        }
1494

1495
        info->subscribed = true;
24✔
1496

1497
        if (info->loaded) {
24✔
1498
                /* The unit was already loaded, synthesize a UnitNew */
1499
                agent_emit_unit_new(agent, info, "virtual");
2✔
1500

1501
                if (info->active_state != _UNIT_ACTIVE_STATE_INVALID) {
2✔
1502
                        agent_emit_unit_state_changed(agent, info, "virtual");
2✔
1503
                }
1504
        }
1505

1506
        return sd_bus_reply_method_return(m, "");
24✔
1507
}
1508

1509
/*************************************************************************
1510
 *** org.eclipse.bluechi.internal.Agent.Unsubscribe ********
1511
 *************************************************************************/
1512

1513
static int agent_method_unsubscribe(sd_bus_message *m, void *userdata, UNUSED sd_bus_error *ret_error) {
29✔
1514
        Agent *agent = userdata;
29✔
1515
        const char *unit = NULL;
29✔
1516
        int r = sd_bus_message_read(m, "s", &unit);
29✔
1517
        if (r < 0) {
29✔
UNCOV
1518
                return sd_bus_reply_method_errorf(
×
1519
                                m, SD_BUS_ERROR_INVALID_ARGS, "Invalid argument for the unit: %s", strerror(-r));
1520
        }
1521

1522
        if (is_wildcard(unit)) {
29✔
1523
                if (!agent->wildcard_subscription_active) {
5✔
1524
                        return sd_bus_reply_method_errorf(
×
1525
                                        m, SD_BUS_ERROR_FAILED, "No wildcard subscription active");
1526
                }
1527
                agent->wildcard_subscription_active = false;
5✔
1528
                return sd_bus_reply_method_return(m, "");
5✔
1529
        }
1530

1531
        _cleanup_free_ char *path = make_unit_path(unit);
53✔
1532
        if (path == NULL) {
24✔
UNCOV
1533
                return sd_bus_reply_method_errorf(m, SD_BUS_ERROR_FAILED, "Failed to create the unit path");
×
1534
        }
1535

1536
        AgentUnitInfo *info = agent_get_unit_info(agent, path);
24✔
1537
        if (info == NULL || !info->subscribed) {
24✔
UNCOV
1538
                return sd_bus_reply_method_errorf(m, SD_BUS_ERROR_FAILED, "Not subscribed");
×
1539
        }
1540

1541
        info->subscribed = false;
24✔
1542
        agent_update_unit_infos_for(agent, info);
24✔
1543
        return sd_bus_reply_method_return(m, "");
24✔
1544
}
1545

1546
/*************************************************************************
1547
 ********** org.eclipse.bluechi.internal.Agent.StartDep ****
1548
 *************************************************************************/
1549

1550
static char *get_dep_unit(const char *unit_name) {
23✔
1551
        return strcat_dup("bluechi-dep@", unit_name);
23✔
1552
}
1553

1554
static int start_dep_callback(sd_bus_message *m, void *userdata, UNUSED sd_bus_error *ret_error) {
12✔
1555
        UNUSED _cleanup_systemd_request_ SystemdRequest *req = userdata;
24✔
1556

1557
        if (sd_bus_message_is_method_error(m, NULL)) {
12✔
1558
                bc_log_errorf("Error starting dep service: %s", sd_bus_message_get_error(m)->message);
2✔
1559
        }
1560
        return 0;
24✔
1561
}
1562

1563
static int agent_method_start_dep(sd_bus_message *m, void *userdata, UNUSED sd_bus_error *ret_error) {
12✔
1564
        Agent *agent = userdata;
12✔
1565
        const char *unit = NULL;
12✔
1566

1567
        int r = sd_bus_message_read(m, "s", &unit);
12✔
1568
        if (r < 0) {
12✔
UNCOV
1569
                return sd_bus_reply_method_errorf(
×
1570
                                m, SD_BUS_ERROR_INVALID_ARGS, "Invalid argument for the unit");
1571
        }
1572

1573
        _cleanup_free_ char *dep_unit = get_dep_unit(unit);
24✔
1574
        if (dep_unit == NULL) {
12✔
UNCOV
1575
                return sd_bus_reply_method_errorf(m, SD_BUS_ERROR_FAILED, "Failed to get the dependency unit");
×
1576
        }
1577

1578
        bc_log_infof("Starting dependency %s", dep_unit);
12✔
1579

1580
        _cleanup_systemd_request_ SystemdRequest *req = agent_create_request(agent, m, "StartUnit");
24✔
1581
        if (req == NULL) {
12✔
UNCOV
1582
                return sd_bus_reply_method_errorf(
×
1583
                                m,
1584
                                SD_BUS_ERROR_FAILED,
1585
                                "Failed to create a systemd request for the StartUnit method");
1586
        }
1587

1588
        r = sd_bus_message_append(req->message, "ss", dep_unit, "replace");
12✔
1589
        if (r < 0) {
12✔
UNCOV
1590
                return sd_bus_reply_method_errorf(
×
1591
                                m,
1592
                                SD_BUS_ERROR_FAILED,
1593
                                "Failed to append the dependency unit and the replace: %s",
1594
                                strerror(-r));
1595
        }
1596

1597
        if (!systemd_request_start(req, start_dep_callback)) {
12✔
UNCOV
1598
                return sd_bus_reply_method_errorf(m, SD_BUS_ERROR_FAILED, "Failed to start a systemd request");
×
1599
        }
1600

1601
        return 0;
1602
}
1603

1604
/*************************************************************************
1605
 **** org.eclipse.bluechi.internal.Agent.StopDep ***********
1606
 *************************************************************************/
1607

1608
static int stop_dep_callback(sd_bus_message *m, void *userdata, UNUSED sd_bus_error *ret_error) {
11✔
1609
        UNUSED _cleanup_systemd_request_ SystemdRequest *req = userdata;
22✔
1610

1611
        if (sd_bus_message_is_method_error(m, NULL)) {
11✔
UNCOV
1612
                bc_log_errorf("Error stopping dep service: %s", sd_bus_message_get_error(m)->message);
×
1613
        }
1614
        return 0;
22✔
1615
}
1616

1617
static int agent_method_stop_dep(sd_bus_message *m, void *userdata, UNUSED sd_bus_error *ret_error) {
11✔
1618
        Agent *agent = userdata;
11✔
1619
        const char *unit = NULL;
11✔
1620

1621
        int r = sd_bus_message_read(m, "s", &unit);
11✔
1622
        if (r < 0) {
11✔
UNCOV
1623
                return sd_bus_reply_method_errorf(
×
1624
                                m, SD_BUS_ERROR_INVALID_ARGS, "Invalid argument for the unit: %s", strerror(-r));
1625
        }
1626

1627
        _cleanup_free_ char *dep_unit = get_dep_unit(unit);
22✔
1628
        if (dep_unit == NULL) {
11✔
UNCOV
1629
                return sd_bus_reply_method_errorf(m, SD_BUS_ERROR_FAILED, "Failed to get the dependency unit");
×
1630
        }
1631

1632
        bc_log_infof("Stopping dependency %s", dep_unit);
11✔
1633

1634
        _cleanup_systemd_request_ SystemdRequest *req = agent_create_request(agent, m, "StopUnit");
22✔
1635
        if (req == NULL) {
11✔
UNCOV
1636
                return sd_bus_reply_method_errorf(
×
1637
                                m,
1638
                                SD_BUS_ERROR_FAILED,
1639
                                "Failed to create a systemd request for the StopUnit method");
1640
        }
1641

1642
        r = sd_bus_message_append(req->message, "ss", dep_unit, "replace");
11✔
1643
        if (r < 0) {
11✔
UNCOV
1644
                return sd_bus_reply_method_errorf(
×
1645
                                m,
1646
                                SD_BUS_ERROR_FAILED,
1647
                                "Failed to append the dependency unit and the replace: %s",
1648
                                strerror(-r));
1649
        }
1650

1651
        if (!systemd_request_start(req, stop_dep_callback)) {
11✔
UNCOV
1652
                return sd_bus_reply_method_errorf(m, SD_BUS_ERROR_FAILED, "Failed to start a systemd request");
×
1653
        }
1654

1655
        return 0;
1656
}
1657

1658

1659
/***************************************************************************
1660
 **** org.eclipse.bluechi.internal.Agent.EnableMetrics *******
1661
 ***************************************************************************/
1662

1663
static const sd_bus_vtable agent_metrics_vtable[] = {
1664
        SD_BUS_VTABLE_START(0),
1665
        SD_BUS_SIGNAL_WITH_NAMES(
1666
                        "AgentJobMetrics",
1667
                        "sst",
1668
                        SD_BUS_PARAM(unit) SD_BUS_PARAM(method) SD_BUS_PARAM(systemd_job_time_micros),
1669
                        0),
1670
        SD_BUS_VTABLE_END
1671
};
1672

1673
static int agent_method_enable_metrics(sd_bus_message *m, void *userdata, UNUSED sd_bus_error *ret_error) {
3✔
1674
        Agent *agent = userdata;
3✔
1675
        int r = 0;
3✔
1676

1677
        if (agent->metrics_enabled) {
3✔
UNCOV
1678
                return sd_bus_reply_method_errorf(m, SD_BUS_ERROR_INVALID_ARGS, "Metrics already enabled");
×
1679
        }
1680
        agent->metrics_enabled = true;
3✔
1681
        r = sd_bus_add_object_vtable(
3✔
1682
                        agent->peer_dbus,
1683
                        &agent->metrics_slot,
1684
                        INTERNAL_AGENT_METRICS_OBJECT_PATH,
1685
                        INTERNAL_AGENT_METRICS_INTERFACE,
1686
                        agent_metrics_vtable,
1687
                        NULL);
1688
        if (r < 0) {
3✔
UNCOV
1689
                bc_log_errorf("Failed to add metrics vtable: %s", strerror(-r));
×
UNCOV
1690
                return r;
×
1691
        }
1692
        bc_log_debug("Metrics enabled");
3✔
1693
        return sd_bus_reply_method_return(m, "");
3✔
1694
}
1695

1696
/***************************************************************************
1697
 **** org.eclipse.bluechi.internal.Agent.DisableMetrics ******
1698
 ***************************************************************************/
1699

1700
static int agent_method_disable_metrics(sd_bus_message *m, void *userdata, UNUSED sd_bus_error *ret_error) {
1✔
1701
        Agent *agent = userdata;
1✔
1702

1703
        if (!agent->metrics_enabled) {
1✔
UNCOV
1704
                return sd_bus_reply_method_errorf(m, SD_BUS_ERROR_INVALID_ARGS, "Metrics already disabled");
×
1705
        }
1706
        agent->metrics_enabled = false;
1✔
1707
        sd_bus_slot_unrefp(&agent->metrics_slot);
1✔
1708
        agent->metrics_slot = NULL;
1✔
1709
        bc_log_debug("Metrics disabled");
1✔
1710
        return sd_bus_reply_method_return(m, "");
1✔
1711
}
1712

1713
/*************************************************************************
1714
 ************** org.eclipse.bluechi.Agent.SetNodeLogLevel  ***************
1715
 *************************************************************************/
1716

1717
static int agent_method_set_log_level(sd_bus_message *m, UNUSED void *userdata, UNUSED sd_bus_error *ret_error) {
3✔
1718
        const char *level = NULL;
3✔
1719
        int r = sd_bus_message_read(m, "s", &level);
3✔
1720
        if (r < 0) {
3✔
UNCOV
1721
                bc_log_errorf("Failed to read Loglevel parameter: %s", strerror(-r));
×
UNCOV
1722
                return sd_bus_reply_method_errorf(
×
1723
                                m, SD_BUS_ERROR_FAILED, "Failed to read Loglevel parameter: %s", strerror(-r));
1724
        }
1725
        LogLevel loglevel = string_to_log_level(level);
3✔
1726
        if (loglevel == LOG_LEVEL_INVALID) {
3✔
1727
                bc_log_errorf("Invalid input for log level: %s", level);
1✔
1728
                return sd_bus_reply_method_errorf(m, SD_BUS_ERROR_FAILED, "Invalid input for log level");
1✔
1729
        }
1730
        bc_log_set_level(loglevel);
2✔
1731
        bc_log_infof("Log level changed to %s", level);
2✔
1732
        return sd_bus_reply_method_return(m, "");
2✔
1733
}
1734

1735

1736
/*******************************************************************
1737
 ************** org.eclipse.bluechi.Agent.JobCancel  ***************
1738
 *******************************************************************/
1739

1740
static int job_cancel_callback(sd_bus_message *m, void *userdata, UNUSED sd_bus_error *ret_error) {
1✔
1741
        _cleanup_systemd_request_ SystemdRequest *req = userdata;
1✔
1742

1743
        if (sd_bus_message_is_method_error(m, NULL)) {
1✔
UNCOV
1744
                return sd_bus_reply_method_error(req->request_message, sd_bus_message_get_error(m));
×
1745
        }
1746

1747
        _cleanup_sd_bus_message_ sd_bus_message *reply = NULL;
1✔
1748
        int r = sd_bus_message_new_method_return(req->request_message, &reply);
1✔
1749
        if (r < 0) {
1✔
UNCOV
1750
                return sd_bus_reply_method_errorf(
×
1751
                                req->request_message,
1752
                                SD_BUS_ERROR_FAILED,
1753
                                "Failed to create a reply message: %s",
1754
                                strerror(-r));
1755
        }
1756

1757
        return sd_bus_message_send(reply);
1✔
1758
}
1759

1760
static JobTracker *agent_find_jobtracker_by_bluechi_id(Agent *agent, uint32_t bc_job_id) {
1✔
1761
        JobTracker *track = NULL;
1✔
1762
        LIST_FOREACH(tracked_jobs, track, agent->tracked_jobs) {
1✔
1763
                AgentJobOp *op = (AgentJobOp *) track->userdata;
1✔
1764
                if (op->bc_job_id == bc_job_id) {
1✔
1765
                        return track;
1766
                }
1767
        }
1768
        return NULL;
1769
}
1770

1771
static int agent_method_job_cancel(sd_bus_message *m, void *userdata, UNUSED sd_bus_error *ret_error) {
1✔
1772
        uint32_t bc_job_id = 0;
1✔
1773
        int r = sd_bus_message_read(m, "u", &bc_job_id);
1✔
1774
        if (r < 0) {
1✔
UNCOV
1775
                bc_log_errorf("Failed to read job ID: %s", strerror(-r));
×
UNCOV
1776
                return sd_bus_reply_method_errorf(
×
1777
                                m, SD_BUS_ERROR_FAILED, "Failed to read job ID: %s", strerror(-r));
1778
        }
1779

1780
        Agent *agent = (Agent *) userdata;
1✔
1781
        JobTracker *tracker = agent_find_jobtracker_by_bluechi_id(agent, bc_job_id);
1✔
1782
        if (tracker == NULL) {
1✔
UNCOV
1783
                return sd_bus_reply_method_errorf(
×
1784
                                m, SD_BUS_ERROR_FAILED, "No job with ID '%d' found", bc_job_id);
1785
        }
1786

1787
        _cleanup_systemd_request_ SystemdRequest *req = agent_create_request_full(
1✔
1788
                        agent, m, tracker->job_object_path, SYSTEMD_JOB_IFACE, "Cancel");
1✔
1789
        if (req == NULL) {
1✔
UNCOV
1790
                return sd_bus_reply_method_errorf(
×
1791
                                m,
1792
                                SD_BUS_ERROR_FAILED,
1793
                                "Failed to create a systemd request for the cancelling job '%d'",
1794
                                bc_job_id);
1795
        }
1796

1797
        if (!systemd_request_start(req, job_cancel_callback)) {
1✔
UNCOV
1798
                return sd_bus_reply_method_errorf(m, SD_BUS_ERROR_FAILED, "Failed to start systemd request");
×
1799
        }
1800

1801
        return sd_bus_reply_method_return(m, "");
1✔
1802
}
1803

1804
static const sd_bus_vtable internal_agent_vtable[] = {
1805
        SD_BUS_VTABLE_START(0),
1806
        SD_BUS_METHOD("ListUnits", "", UNIT_INFO_STRUCT_ARRAY_TYPESTRING, agent_method_list_units, 0),
1807
        SD_BUS_METHOD("ListUnitFiles", "", UNIT_FILE_INFO_STRUCT_ARRAY_TYPESTRING, agent_method_list_unit_files, 0),
1808
        SD_BUS_METHOD("GetUnitFileState", "s", "s", agent_method_passthrough_to_systemd, 0),
1809
        SD_BUS_METHOD("GetUnitProperties", "ss", "a{sv}", agent_method_get_unit_properties, 0),
1810
        SD_BUS_METHOD("GetUnitProperty", "sss", "v", agent_method_get_unit_property, 0),
1811
        SD_BUS_METHOD("SetUnitProperties", "sba(sv)", "", agent_method_set_unit_properties, 0),
1812
        SD_BUS_METHOD("StartUnit", "ssu", "", agent_method_start_unit, 0),
1813
        SD_BUS_METHOD("StartTransientUnit", "ssa(sv)a(sa(sv))", "o", agent_method_passthrough_to_systemd, 0),
1814
        SD_BUS_METHOD("StopUnit", "ssu", "", agent_method_stop_unit, 0),
1815
        SD_BUS_METHOD("FreezeUnit", "s", "", agent_method_freeze_unit, 0),
1816
        SD_BUS_METHOD("ThawUnit", "s", "", agent_method_thaw_unit, 0),
1817
        SD_BUS_METHOD("RestartUnit", "ssu", "", agent_method_restart_unit, 0),
1818
        SD_BUS_METHOD("ReloadUnit", "ssu", "", agent_method_reload_unit, 0),
1819
        SD_BUS_METHOD("Subscribe", "s", "", agent_method_subscribe, 0),
1820
        SD_BUS_METHOD("Unsubscribe", "s", "", agent_method_unsubscribe, 0),
1821
        SD_BUS_METHOD("EnableMetrics", "", "", agent_method_enable_metrics, 0),
1822
        SD_BUS_METHOD("DisableMetrics", "", "", agent_method_disable_metrics, 0),
1823
        SD_BUS_METHOD("SetLogLevel", "s", "", agent_method_set_log_level, 0),
1824
        SD_BUS_METHOD("JobCancel", "u", "", agent_method_job_cancel, 0),
1825
        SD_BUS_METHOD("EnableUnitFiles", "asbb", "ba(sss)", agent_method_passthrough_to_systemd, 0),
1826
        SD_BUS_METHOD("DisableUnitFiles", "asb", "a(sss)", agent_method_passthrough_to_systemd, 0),
1827
        SD_BUS_METHOD("Reload", "", "", agent_method_passthrough_to_systemd, 0),
1828
        SD_BUS_METHOD("ResetFailed", "", "", agent_method_passthrough_to_systemd, 0),
1829
        SD_BUS_METHOD("ResetFailedUnit", "s", "", agent_method_passthrough_to_systemd, 0),
1830
        SD_BUS_METHOD("KillUnit", "ssi", "", agent_method_passthrough_to_systemd, 0),
1831
        SD_BUS_METHOD("StartDep", "s", "", agent_method_start_dep, 0),
1832
        SD_BUS_METHOD("StopDep", "s", "", agent_method_stop_dep, 0),
1833
        SD_BUS_METHOD("GetDefaultTarget", "", "s", agent_method_passthrough_to_systemd, 0),
1834
        SD_BUS_METHOD("SetDefaultTarget", "sb", "a(sss)", agent_method_passthrough_to_systemd, 0),
1835
        SD_BUS_SIGNAL_WITH_NAMES("JobDone", "us", SD_BUS_PARAM(id) SD_BUS_PARAM(result), 0),
1836
        SD_BUS_SIGNAL_WITH_NAMES("JobStateChanged", "us", SD_BUS_PARAM(id) SD_BUS_PARAM(state), 0),
1837
        SD_BUS_SIGNAL_WITH_NAMES(
1838
                        "UnitPropertiesChanged",
1839
                        "ssa{sv}",
1840
                        SD_BUS_PARAM(unit) SD_BUS_PARAM(iface) SD_BUS_PARAM(properties),
1841
                        0),
1842
        SD_BUS_SIGNAL_WITH_NAMES("UnitNew", "ss", SD_BUS_PARAM(unit) SD_BUS_PARAM(reason), 0),
1843
        SD_BUS_SIGNAL_WITH_NAMES(
1844
                        "UnitStateChanged",
1845
                        "ssss",
1846
                        SD_BUS_PARAM(unit) SD_BUS_PARAM(active_state) SD_BUS_PARAM(substate)
1847
                                        SD_BUS_PARAM(reason),
1848
                        0),
1849
        SD_BUS_SIGNAL_WITH_NAMES("UnitRemoved", "s", SD_BUS_PARAM(unit), 0),
1850
        SD_BUS_SIGNAL("Heartbeat", "", 0),
1851
        SD_BUS_VTABLE_END
1852
};
1853

1854
/*************************************************************************
1855
 ********** org.eclipse.bluechi.Agent.CreateProxy **********
1856
 *************************************************************************/
1857

1858
static ProxyService *agent_find_proxy(
21✔
1859
                Agent *agent, const char *local_service_name, const char *node_name, const char *unit_name) {
1860
        ProxyService *proxy = NULL;
21✔
1861

1862
        LIST_FOREACH(proxy_services, proxy, agent->proxy_services) {
21✔
1863
                if (streq(proxy->local_service_name, local_service_name) &&
5✔
1864
                    streq(proxy->node_name, node_name) && streq(proxy->unit_name, unit_name)) {
5✔
1865
                        return proxy;
1866
                }
1867
        }
1868

1869
        return NULL;
1870
}
1871

1872
static int agent_method_create_proxy(UNUSED sd_bus_message *m, void *userdata, UNUSED sd_bus_error *ret_error) {
13✔
1873
        Agent *agent = (Agent *) userdata;
13✔
1874
        _cleanup_sd_bus_message_ sd_bus_message *reply = NULL;
26✔
1875
        const char *local_service_name = NULL;
13✔
1876
        const char *node_name = NULL;
13✔
1877
        const char *unit_name = NULL;
13✔
1878
        int r = sd_bus_message_read(m, "sss", &local_service_name, &node_name, &unit_name);
13✔
1879
        if (r < 0) {
13✔
UNCOV
1880
                return sd_bus_reply_method_errorf(
×
1881
                                m,
1882
                                SD_BUS_ERROR_INVALID_ARGS,
1883
                                "Invalid argument for: local service name, node name, or unit name: %s",
1884
                                strerror(-r));
1885
        }
1886

1887
        bc_log_infof("CreateProxy request from %s", local_service_name);
13✔
1888

1889
        ProxyService *old_proxy = agent_find_proxy(agent, local_service_name, node_name, unit_name);
13✔
1890
        if (old_proxy) {
13✔
UNCOV
1891
                return sd_bus_reply_method_errorf(reply, SD_BUS_ERROR_ADDRESS_IN_USE, "Proxy already exists");
×
1892
        }
1893

1894
        _cleanup_proxy_service_ ProxyService *proxy = proxy_service_new(
26✔
1895
                        agent, local_service_name, node_name, unit_name, m);
1896
        if (proxy == NULL) {
13✔
UNCOV
1897
                return sd_bus_reply_method_errorf(
×
1898
                                reply, SD_BUS_ERROR_FAILED, "Failed to create a proxy service");
1899
        }
1900

1901
        if (!proxy_service_export(proxy)) {
13✔
UNCOV
1902
                return sd_bus_reply_method_errorf(
×
1903
                                reply, SD_BUS_ERROR_FAILED, "Failed to export a proxy service");
1904
        }
1905

1906
        r = proxy_service_emit_proxy_new(proxy);
13✔
1907
        if (r < 0 && r != -ENOTCONN) {
13✔
UNCOV
1908
                bc_log_errorf("Failed to emit ProxyNew signal: %s", strerror(-r));
×
UNCOV
1909
                return sd_bus_reply_method_errorf(
×
1910
                                m, SD_BUS_ERROR_FAILED, "Failed to emit a proxy service: %s", strerror(-r));
1911
        }
1912

1913
        LIST_APPEND(proxy_services, agent->proxy_services, proxy_service_ref(proxy));
13✔
1914

1915
        return 1;
1916
}
1917

1918
/*************************************************************************
1919
 **** org.eclipse.bluechi.Agent.RemoveProxy ****************
1920
 *************************************************************************/
1921

1922
/* This is called when the proxy service is shut down, via the
1923
 * `ExecStop=bluechi-proxy remove` command being called. This will
1924
 * happen when no services depends on the proxy anymore, as well as
1925
 * when the agent explicitly tells the proxy to stop.
1926
 */
1927

1928
static int agent_method_remove_proxy(sd_bus_message *m, UNUSED void *userdata, UNUSED sd_bus_error *ret_error) {
8✔
1929
        Agent *agent = (Agent *) userdata;
8✔
1930
        const char *local_service_name = NULL;
8✔
1931
        const char *node_name = NULL;
8✔
1932
        const char *unit_name = NULL;
8✔
1933
        int r = sd_bus_message_read(m, "sss", &local_service_name, &node_name, &unit_name);
8✔
1934
        if (r < 0) {
8✔
UNCOV
1935
                return sd_bus_reply_method_errorf(
×
1936
                                m,
1937
                                SD_BUS_ERROR_INVALID_ARGS,
1938
                                "Invalid argument for: local service name, node name, or unit name: %s",
1939
                                strerror(-r));
1940
        }
1941

1942
        bc_log_infof("RemoveProxy request from %s", local_service_name);
8✔
1943

1944
        ProxyService *proxy = agent_find_proxy(agent, local_service_name, node_name, unit_name);
8✔
1945
        if (proxy == NULL) {
8✔
1946
                /* NOTE: This happens pretty often, when we stopped the proxy service ourselves
1947
                   and removed the proxy, but then the ExecStop triggers, so we sent a success here. */
1948
                return sd_bus_reply_method_return(m, "");
3✔
1949
        }
1950

1951
        /* We got this because the proxy is shutting down, no need to manually do it */
1952
        proxy->dont_stop_proxy = true;
5✔
1953

1954
        agent_remove_proxy(agent, proxy, true);
5✔
1955

1956
        return sd_bus_reply_method_return(m, "");
5✔
1957
}
1958

1959

1960
/*************************************************************************
1961
 **** org.eclipse.bluechi.Agent.SwitchController ******
1962
 *************************************************************************/
1963

1964
static int agent_method_switch_controller(sd_bus_message *m, void *userdata, UNUSED sd_bus_error *ret_error) {
1✔
1965
        Agent *agent = userdata;
1✔
1966
        const char *dbus_address = NULL;
1✔
1967

1968
        int r = sd_bus_message_read(m, "s", &dbus_address);
1✔
1969
        if (r < 0) {
1✔
1970
                bc_log_errorf("Failed to read D-Bus address parameter: %s", strerror(-r));
×
UNCOV
1971
                return sd_bus_reply_method_errorf(
×
1972
                                m,
1973
                                SD_BUS_ERROR_FAILED,
1974
                                "Failed to read D-Bus address parameter: %s",
1975
                                strerror(-r));
1976
        }
1977

1978
        if (agent->assembled_controller_address && streq(agent->assembled_controller_address, dbus_address)) {
1✔
UNCOV
1979
                return sd_bus_reply_method_errorf(
×
1980
                                m,
1981
                                SD_BUS_ERROR_FAILED,
1982
                                "Failed to switch controller because already connected to the controller");
1983
        }
1984

1985
        /* The assembled controller address is the field used for the ControllerAddress property.
1986
         * However, this field gets assembled in the reconnect based on the configuration of host + port
1987
         * or controller address.
1988
         * So in order to enable emitting the address changed signal be sent before the disconnect AND keep
1989
         * the new value, both fields need to be set with the new address.
1990
         */
1991
        if (!agent_set_assembled_controller_address(agent, dbus_address) ||
2✔
1992
            !agent_set_controller_address(agent, dbus_address)) {
1✔
UNCOV
1993
                bc_log_error("Failed to set controller address");
×
UNCOV
1994
                return sd_bus_reply_method_errorf(m, SD_BUS_ERROR_FAILED, "Failed to set controller address");
×
1995
        }
1996
        r = sd_bus_emit_properties_changed(
1✔
1997
                        agent->api_bus, BC_AGENT_OBJECT_PATH, AGENT_INTERFACE, "ControllerAddress", NULL);
1998
        if (r < 0) {
1✔
UNCOV
1999
                bc_log_errorf("Failed to emit controller address property changed: %s", strerror(-r));
×
2000
        }
2001

2002
        agent_disconnected(NULL, userdata, NULL);
1✔
2003

2004
        return sd_bus_reply_method_return(m, "");
1✔
2005
}
2006

2007

2008
/*************************************************************************
2009
 **** org.eclipse.bluechi.Agent.Status ****************
2010
 *************************************************************************/
2011

2012
static int agent_property_get_status(
324✔
2013
                UNUSED sd_bus *bus,
2014
                UNUSED const char *path,
2015
                UNUSED const char *interface,
2016
                UNUSED const char *property,
2017
                sd_bus_message *reply,
2018
                void *userdata,
2019
                UNUSED sd_bus_error *ret_error) {
2020
        Agent *agent = userdata;
324✔
2021

2022
        return sd_bus_message_append(reply, "s", agent_is_online(agent));
324✔
2023
}
2024

2025
/*************************************************************************
2026
 **** org.eclipse.bluechi.Agent.LogLevel ****************
2027
 *************************************************************************/
2028

2029
static int agent_property_get_log_level(
2✔
2030
                UNUSED sd_bus *bus,
2031
                UNUSED const char *path,
2032
                UNUSED const char *interface,
2033
                UNUSED const char *property,
2034
                sd_bus_message *reply,
2035
                UNUSED void *userdata,
2036
                UNUSED sd_bus_error *ret_error) {
2037
        const char *log_level = log_level_to_string(bc_log_get_level());
2✔
2038
        return sd_bus_message_append(reply, "s", log_level);
2✔
2039
}
2040

2041
/*************************************************************************
2042
 **** org.eclipse.bluechi.Agent.LogTarget ****************
2043
 *************************************************************************/
2044

2045
static int agent_property_get_log_target(
1✔
2046
                UNUSED sd_bus *bus,
2047
                UNUSED const char *path,
2048
                UNUSED const char *interface,
2049
                UNUSED const char *property,
2050
                sd_bus_message *reply,
2051
                UNUSED void *userdata,
2052
                UNUSED sd_bus_error *ret_error) {
2053
        return sd_bus_message_append(reply, "s", log_target_to_str(bc_log_get_log_fn()));
1✔
2054
}
2055

2056

2057
static const sd_bus_vtable agent_vtable[] = {
2058
        SD_BUS_VTABLE_START(0),
2059
        SD_BUS_METHOD("CreateProxy", "sss", "", agent_method_create_proxy, 0),
2060
        SD_BUS_METHOD("RemoveProxy", "sss", "", agent_method_remove_proxy, 0),
2061
        SD_BUS_METHOD("JobCancel", "u", "", agent_method_job_cancel, 0),
2062
        SD_BUS_METHOD("SwitchController", "s", "", agent_method_switch_controller, 0),
2063

2064
        SD_BUS_PROPERTY("Status", "s", agent_property_get_status, 0, SD_BUS_VTABLE_PROPERTY_EMITS_CHANGE),
2065
        SD_BUS_PROPERTY("LogLevel", "s", agent_property_get_log_level, 0, SD_BUS_VTABLE_PROPERTY_EXPLICIT),
2066
        SD_BUS_PROPERTY("LogTarget", "s", agent_property_get_log_target, 0, SD_BUS_VTABLE_PROPERTY_CONST),
2067
        SD_BUS_PROPERTY("DisconnectTimestamp",
2068
                        "t",
2069
                        NULL,
2070
                        offsetof(Agent, disconnect_timestamp),
2071
                        SD_BUS_VTABLE_PROPERTY_EXPLICIT),
2072
        SD_BUS_PROPERTY("DisconnectTimestampMonotonic",
2073
                        "t",
2074
                        NULL,
2075
                        offsetof(Agent, disconnect_timestamp_monotonic),
2076
                        SD_BUS_VTABLE_PROPERTY_EXPLICIT),
2077
        SD_BUS_PROPERTY("LastSeenTimestamp",
2078
                        "t",
2079
                        NULL,
2080
                        offsetof(Agent, controller_last_seen),
2081
                        SD_BUS_VTABLE_PROPERTY_EXPLICIT),
2082
        SD_BUS_PROPERTY("LastSeenTimestampMonotonic",
2083
                        "t",
2084
                        NULL,
2085
                        offsetof(Agent, controller_last_seen_monotonic),
2086
                        SD_BUS_VTABLE_PROPERTY_EXPLICIT),
2087
        SD_BUS_PROPERTY("ControllerAddress",
2088
                        "s",
2089
                        NULL,
2090
                        offsetof(Agent, assembled_controller_address),
2091
                        SD_BUS_VTABLE_PROPERTY_EMITS_CHANGE),
2092
        SD_BUS_VTABLE_END
2093
};
2094

2095

2096
static void job_tracker_free(JobTracker *track) {
63✔
2097
        if (track->userdata && track->free_userdata) {
63✔
2098
                track->free_userdata(track->userdata);
63✔
2099
        }
2100
        free_and_null(track->job_object_path);
63✔
2101
        free(track);
63✔
2102
}
63✔
2103

UNCOV
2104
DEFINE_CLEANUP_FUNC(JobTracker, job_tracker_free)
×
2105
#define _cleanup_job_tracker_ _cleanup_(job_tracker_freep)
2106

2107
static bool
2108
                agent_track_job(Agent *agent,
66✔
2109
                                const char *job_object_path,
2110
                                job_tracker_callback callback,
2111
                                void *userdata,
2112
                                free_func_t free_userdata) {
2113
        _cleanup_job_tracker_ JobTracker *track = malloc0(sizeof(JobTracker));
132✔
2114
        if (track == NULL) {
66✔
2115
                return false;
2116
        }
2117

2118
        track->job_object_path = strdup(job_object_path);
66✔
2119
        if (track->job_object_path == NULL) {
66✔
2120
                return false;
2121
        }
2122

2123
// Disabling -Wanalyzer-malloc-leak temporarily due to false-positive
2124
//      Leak detected is based on steal_pointer setting track=NULL and, subsequently,
2125
//      not freeing allocated job_object_path. This is desired since the actual instance
2126
//      is added to and managed by the tracked_jobs list of the agent.
2127
#pragma GCC diagnostic push
2128
#pragma GCC diagnostic ignored "-Wanalyzer-malloc-leak"
2129

2130
        track->callback = callback;
66✔
2131
        track->userdata = userdata;
66✔
2132
        track->free_userdata = free_userdata;
66✔
2133
        LIST_INIT(tracked_jobs, track);
66✔
2134

2135
        LIST_PREPEND(tracked_jobs, agent->tracked_jobs, steal_pointer(&track));
66✔
2136

2137
        return true;
66✔
2138
}
2139
#pragma GCC diagnostic pop
2140

2141

2142
static int agent_match_job_changed(sd_bus_message *m, void *userdata, UNUSED sd_bus_error *ret_error) {
37✔
2143
        Agent *agent = userdata;
37✔
2144
        const char *interface = NULL;
37✔
2145
        const char *state = NULL;
37✔
2146
        const char *object_path = sd_bus_message_get_path(m);
37✔
2147
        JobTracker *track = NULL;
37✔
2148
        AgentJobOp *op = NULL;
37✔
2149

2150
        int r = sd_bus_message_read(m, "s", &interface);
37✔
2151
        if (r < 0) {
37✔
UNCOV
2152
                bc_log_errorf("Failed to read job property: %s", strerror(-r));
×
UNCOV
2153
                return r;
×
2154
        }
2155

2156
        /* Only handle Job iface changes */
2157
        if (!streq(interface, SYSTEMD_JOB_IFACE)) {
37✔
2158
                return 0;
2159
        }
2160

2161
        /* Look for tracked jobs */
2162
        LIST_FOREACH(tracked_jobs, track, agent->tracked_jobs) {
50✔
2163
                if (streq(track->job_object_path, object_path)) {
18✔
2164
                        op = track->userdata;
5✔
2165
                        break;
5✔
2166
                }
2167
        }
2168

2169
        if (op == NULL) {
37✔
2170
                return 0;
2171
        }
2172

2173
        r = bus_parse_property_string(m, "State", &state);
5✔
2174
        if (r < 0) {
5✔
UNCOV
2175
                if (r == -ENOENT) {
×
2176
                        return 0; /* Some other property changed */
2177
                }
UNCOV
2178
                bc_log_errorf("Failed to get job property: %s", strerror(-r));
×
UNCOV
2179
                return r;
×
2180
        }
2181

2182
        r = sd_bus_emit_signal(
5✔
2183
                        agent->peer_dbus,
2184
                        INTERNAL_AGENT_OBJECT_PATH,
2185
                        INTERNAL_AGENT_INTERFACE,
2186
                        "JobStateChanged",
2187
                        "us",
2188
                        op->bc_job_id,
2189
                        state);
2190
        if (r < 0) {
5✔
2191
                bc_log_errorf("Failed to emit JobStateChanged: %s", strerror(-r));
1✔
2192
        }
2193

2194
        return 0;
2195
}
2196

2197
static int agent_match_unit_changed(sd_bus_message *m, void *userdata, UNUSED sd_bus_error *ret_error) {
2,313✔
2198
        Agent *agent = userdata;
2,313✔
2199
        const char *interface = NULL;
2,313✔
2200

2201
        int r = sd_bus_message_read(m, "s", &interface);
2,313✔
2202
        if (r < 0) {
2,313✔
2203
                return r;
2,313✔
2204
        }
2205

2206
        AgentUnitInfo *info = agent_get_unit_info(agent, sd_bus_message_get_path(m));
2,313✔
2207
        if (info == NULL) {
2,313✔
2208
                return 0;
2209
        }
2210

2211
        if (streq(interface, "org.freedesktop.systemd1.Unit")) {
2,313✔
2212
                bool state_changed = unit_info_update_state(info, m);
1,490✔
2213
                if (state_changed && (info->subscribed || agent->wildcard_subscription_active)) {
1,490✔
2214
                        agent_emit_unit_state_changed(agent, info, "real");
49✔
2215
                }
2216
        }
2217

2218
        if (!info->subscribed && !agent->wildcard_subscription_active) {
2,313✔
2219
                return 0;
2220
        }
2221

2222
        /* Rewind and skip to copy content again */
2223
        (void) sd_bus_message_rewind(m, true);
220✔
2224
        (void) sd_bus_message_skip(m, "s"); // re-skip interface
220✔
2225

2226
        bc_log_debugf("Sending UnitPropertiesChanged %s", info->unit);
220✔
2227

2228
        /* Forward the property changes */
2229
        _cleanup_sd_bus_message_ sd_bus_message *sig = NULL;
2,313✔
2230
        r = sd_bus_message_new_signal(
220✔
2231
                        agent->peer_dbus,
2232
                        &sig,
2233
                        INTERNAL_AGENT_OBJECT_PATH,
2234
                        INTERNAL_AGENT_INTERFACE,
2235
                        "UnitPropertiesChanged");
2236
        if (r < 0) {
220✔
2237
                return r;
2238
        }
2239

2240
        r = sd_bus_message_append(sig, "s", info->unit);
220✔
2241
        if (r < 0) {
220✔
2242
                return r;
2243
        }
2244

2245
        r = sd_bus_message_append(sig, "s", interface);
220✔
2246
        if (r < 0) {
220✔
2247
                return r;
2248
        }
2249

2250
        r = sd_bus_message_copy(sig, m, false);
220✔
2251
        if (r < 0) {
220✔
2252
                return r;
2253
        }
2254

2255
        return sd_bus_send(agent->peer_dbus, sig, NULL);
220✔
2256
}
2257

2258
static int agent_match_unit_new(sd_bus_message *m, void *userdata, UNUSED sd_bus_error *error) {
9,230✔
2259
        Agent *agent = userdata;
9,230✔
2260
        const char *unit_name = NULL;
9,230✔
2261
        const char *path = NULL;
9,230✔
2262

2263
        int r = sd_bus_message_read(m, "so", &unit_name, &path);
9,230✔
2264
        if (r < 0) {
9,230✔
2265
                return r;
9,230✔
2266
        }
2267

2268
        AgentUnitInfo *info = agent_ensure_unit_info(agent, unit_name);
9,230✔
2269
        if (info == NULL) {
9,230✔
2270
                return 0;
2271
        }
2272

2273
        info->loaded = true;
9,230✔
2274

2275
// Disabling -Wanalyzer-malloc-leak temporarily due to false-positive
2276
//      Leak detection does not take into account that the AgentUnitInfo info instance was
2277
//      added to and is managed by the hashmap
2278
#pragma GCC diagnostic push
2279
#pragma GCC diagnostic ignored "-Wanalyzer-malloc-leak"
2280

2281
        /* Systemd services start in inactive(dead) state, so use this as the first
2282
         * state. This way we can detect when the state changed without sporadic
2283
         * changes to "dead".
2284
         */
2285
        info->active_state = UNIT_INACTIVE;
9,230✔
2286
        if (info->substate != NULL) {
9,230✔
UNCOV
2287
                free_and_null(info->substate);
×
2288
        }
2289
        info->substate = strdup("dead");
9,230✔
2290

2291
        if (info->subscribed || agent->wildcard_subscription_active) {
9,230✔
2292
                /* Forward the event */
2293
                agent_emit_unit_new(agent, info, "real");
27✔
2294
        }
2295

2296

2297
        return 0;
2298
}
2299
#pragma GCC diagnostic pop
2300

2301
static int agent_match_unit_removed(sd_bus_message *m, void *userdata, UNUSED sd_bus_error *error) {
9,178✔
2302
        Agent *agent = userdata;
9,178✔
2303
        const char *id = NULL;
9,178✔
2304
        const char *path = NULL;
9,178✔
2305

2306
        int r = sd_bus_message_read(m, "so", &id, &path);
9,178✔
2307
        if (r < 0) {
9,178✔
2308
                return r;
9,178✔
2309
        }
2310

2311
        AgentUnitInfo *info = agent_get_unit_info(agent, path);
9,178✔
2312
        if (info == NULL) {
9,178✔
2313
                return 0;
2314
        }
2315

2316
        info->loaded = false;
9,178✔
2317
        info->active_state = _UNIT_ACTIVE_STATE_INVALID;
9,178✔
2318
        free_and_null(info->substate);
9,178✔
2319

2320
        if (info->subscribed || agent->wildcard_subscription_active) {
9,178✔
2321
                /* Forward the event */
2322
                agent_emit_unit_removed(agent, info);
19✔
2323
        }
2324

2325
        /* Maybe remove if unloaded and no other interest in it */
2326
        agent_update_unit_infos_for(agent, info);
9,178✔
2327

2328
        return 1;
2329
}
2330

2331

2332
static int agent_match_job_removed(sd_bus_message *m, void *userdata, UNUSED sd_bus_error *error) {
147✔
2333
        Agent *agent = userdata;
147✔
2334
        const char *job_path = NULL;
147✔
2335
        const char *unit = NULL;
147✔
2336
        const char *result = NULL;
147✔
2337
        JobTracker *track = NULL, *next_track = NULL;
147✔
2338
        uint32_t id = 0;
147✔
2339
        int r = 0;
147✔
2340

2341
        r = sd_bus_message_read(m, "uoss", &id, &job_path, &unit, &result);
147✔
2342
        if (r < 0) {
147✔
UNCOV
2343
                bc_log_errorf("Can't parse job result: %s", strerror(-r));
×
UNCOV
2344
                return r;
×
2345
        }
2346

2347
        (void) sd_bus_message_rewind(m, true);
147✔
2348

2349
        LIST_FOREACH_SAFE(tracked_jobs, track, next_track, agent->tracked_jobs) {
173✔
2350
                if (streq(track->job_object_path, job_path)) {
89✔
2351
                        LIST_REMOVE(tracked_jobs, agent->tracked_jobs, track);
63✔
2352
                        track->callback(m, result, track->userdata);
63✔
2353
                        job_tracker_free(track);
63✔
2354
                        break;
2355
                }
2356
        }
2357

2358
        return 0;
2359
}
2360

2361
static int agent_match_heartbeat(UNUSED sd_bus_message *m, void *userdata, UNUSED sd_bus_error *error) {
2✔
2362
        Agent *agent = userdata;
2✔
2363
        uint64_t now = 0;
2✔
2364
        uint64_t now_monotonic = 0;
2✔
2365

2366
        now = get_time_micros();
2✔
2367
        if (now == USEC_INFINITY) {
2✔
UNCOV
2368
                bc_log_error("Failed to get current time on heartbeat");
×
UNCOV
2369
                return 0;
×
2370
        }
2371

2372
        now_monotonic = get_time_micros_monotonic();
2✔
2373
        if (now_monotonic == USEC_INFINITY) {
2✔
UNCOV
2374
                bc_log_error("Failed to get current monotonic time on heartbeat");
×
UNCOV
2375
                return 0;
×
2376
        }
2377

2378
        agent->controller_last_seen = now;
2✔
2379
        agent->controller_last_seen_monotonic = now_monotonic;
2✔
2380
        return 1;
2✔
2381
}
2382

2383
static int debug_systemd_message_handler(
2384
                sd_bus_message *m, UNUSED void *userdata, UNUSED sd_bus_error *ret_error) {
2385
        bc_log_infof("Incoming message from systemd: path: %s, iface: %s, member: %s, signature: '%s'",
2386
                     sd_bus_message_get_path(m),
2387
                     sd_bus_message_get_interface(m),
2388
                     sd_bus_message_get_member(m),
2389
                     sd_bus_message_get_signature(m, true));
2390
        if (DEBUG_SYSTEMD_MESSAGES_CONTENT) {
2391
                sd_bus_message_dump(m, stderr, 0);
2392
                sd_bus_message_rewind(m, true);
2393
        }
2394
        return 0;
2395
}
2396

2397
int agent_init_units(Agent *agent, sd_bus_message *m) {
150✔
2398
        int r = sd_bus_message_enter_container(m, SD_BUS_TYPE_ARRAY, UNIT_INFO_STRUCT_TYPESTRING);
150✔
2399
        if (r < 0) {
150✔
2400
                return r;
2401
        }
2402

2403
// Disabling -Wanalyzer-malloc-leak temporarily due to false-positive
2404
//      Leak detection does not take into account that the AgentUnitInfo info instance was
2405
//      added to and is managed by the hashmap
2406
#pragma GCC diagnostic push
2407
#pragma GCC diagnostic ignored "-Wanalyzer-malloc-leak"
2408
        while (sd_bus_message_at_end(m, false) == 0) {
17,260✔
2409
                r = sd_bus_message_enter_container(m, SD_BUS_TYPE_STRUCT, UNIT_INFO_TYPESTRING);
17,110✔
2410
                if (r < 0) {
17,110✔
UNCOV
2411
                        return r;
×
2412
                }
2413

2414
                // NOLINTBEGIN(cppcoreguidelines-init-variables)
2415
                const char *name, *desc, *load_state, *active_state, *sub_state, *follower, *object_path,
17,110✔
2416
                                *job_type, *job_object_path;
2417
                uint32_t job_id;
17,110✔
2418
                // NOLINTEND(cppcoreguidelines-init-variables)
2419

2420
                r = sd_bus_message_read(
17,110✔
2421
                                m,
2422
                                UNIT_INFO_TYPESTRING,
2423
                                &name,
2424
                                &desc,
2425
                                &load_state,
2426
                                &active_state,
2427
                                &sub_state,
2428
                                &follower,
2429
                                &object_path,
2430
                                &job_id,
2431
                                &job_type,
2432
                                &job_object_path);
2433
                if (r < 0) {
17,110✔
2434
                        return r;
2435
                }
2436

2437
                AgentUnitInfo *info = agent_ensure_unit_info(agent, name);
17,110✔
2438
                if (info) {
17,110✔
2439
                        assert(streq(info->object_path, object_path));
17,110✔
2440
                        info->loaded = true;
17,110✔
2441
                        info->active_state = active_state_from_string(active_state);
17,110✔
2442
                        if (info->substate != NULL) {
17,110✔
UNCOV
2443
                                free_and_null(info->substate);
×
2444
                        }
2445
                        info->substate = strdup(sub_state);
17,110✔
2446
                }
2447

2448
                r = sd_bus_message_exit_container(m);
17,110✔
2449
                if (r < 0) {
17,110✔
2450
                        return r;
2451
                }
2452
        }
2453
#pragma GCC diagnostic pop
2454

2455
        r = sd_bus_message_exit_container(m);
150✔
2456
        if (r < 0) {
150✔
UNCOV
2457
                return r;
×
2458
        }
2459

2460
        return 0;
2461
}
2462

2463
static bool ensure_assembled_controller_address(Agent *agent) {
174✔
2464
        int r = 0;
174✔
2465

2466
        if (agent->assembled_controller_address != NULL) {
174✔
2467
                return true;
174✔
2468
        }
2469

2470
        if (agent->controller_address != NULL) {
173✔
2471
                return agent_set_assembled_controller_address(agent, agent->controller_address);
7✔
2472
        }
2473

2474
        if (agent->host == NULL) {
166✔
UNCOV
2475
                bc_log_errorf("No controller host specified for agent '%s'", agent->name);
×
UNCOV
2476
                return false;
×
2477
        }
2478

2479
        char *ip_address = agent->host;
166✔
2480
        bool host_is_ipv4 = is_ipv4(ip_address);
166✔
2481
        bool host_is_ipv6 = is_ipv6(ip_address);
166✔
2482

2483
        _cleanup_free_ char *resolved_ip_address = NULL;
174✔
2484
        if (!host_is_ipv4 && !host_is_ipv6) {
166✔
2485
                int r = get_address(ip_address, &resolved_ip_address, getaddrinfo);
4✔
2486
                if (r < 0) {
4✔
UNCOV
2487
                        bc_log_errorf("Failed to get IP address from host '%s': %s", agent->host, strerror(-r));
×
2488
                        return false;
2489
                }
2490
                bc_log_infof("Translated '%s' to '%s'", ip_address, resolved_ip_address);
4✔
2491
                ip_address = resolved_ip_address;
4✔
2492
        }
2493

2494
        if (host_is_ipv4 || is_ipv4(ip_address)) {
166✔
2495
                struct sockaddr_in host;
162✔
2496
                memset(&host, 0, sizeof(host));
162✔
2497
                host.sin_family = AF_INET;
162✔
2498
                host.sin_port = htons(agent->port);
162✔
2499
                r = inet_pton(AF_INET, ip_address, &host.sin_addr);
162✔
2500
                if (r < 1) {
162✔
UNCOV
2501
                        bc_log_errorf("INET4: Invalid host option '%s'", ip_address);
×
UNCOV
2502
                        return false;
×
2503
                }
2504
                _cleanup_free_ char *assembled_controller_address = assemble_tcp_address(&host);
324✔
2505
                if (assembled_controller_address == NULL) {
162✔
UNCOV
2506
                        return false;
×
2507
                }
2508
                agent_set_assembled_controller_address(agent, assembled_controller_address);
162✔
2509
        } else if (host_is_ipv6 || is_ipv6(ip_address)) {
4✔
2510
                struct sockaddr_in6 host6;
4✔
2511
                memset(&host6, 0, sizeof(host6));
4✔
2512
                host6.sin6_family = AF_INET6;
4✔
2513
                host6.sin6_port = htons(agent->port);
4✔
2514
                r = inet_pton(AF_INET6, ip_address, &host6.sin6_addr);
4✔
2515
                if (r < 1) {
4✔
2516
                        bc_log_errorf("INET6: Invalid host option '%s'", ip_address);
×
UNCOV
2517
                        return false;
×
2518
                }
2519
                _cleanup_free_ char *assembled_controller_address = assemble_tcp_address_v6(&host6);
8✔
2520
                if (assembled_controller_address == NULL) {
4✔
UNCOV
2521
                        return false;
×
2522
                }
2523
                agent_set_assembled_controller_address(agent, assembled_controller_address);
4✔
2524
        } else {
UNCOV
2525
                bc_log_errorf("Unknown protocol for '%s'", ip_address);
×
2526
        }
2527

2528
        return agent->assembled_controller_address != NULL;
166✔
2529
}
2530

2531
bool agent_start(Agent *agent) {
150✔
2532
        int r = 0;
150✔
2533
        sd_bus_error error = SD_BUS_ERROR_NULL;
150✔
2534

2535
        bc_log_infof("Starting bluechi-agent %s", CONFIG_H_BC_VERSION);
150✔
2536

2537
        if (agent == NULL) {
150✔
2538
                return false;
150✔
2539
        }
2540

2541
        if (agent->name == NULL) {
150✔
UNCOV
2542
                bc_log_error("No agent name specified");
×
2543
                return false;
×
2544
        }
2545

2546
        if (!ensure_assembled_controller_address(agent)) {
150✔
2547
                return false;
2548
        }
2549

2550
        /* If systemd --user, we need to be on the user bus for the proxy to work */
2551
        if (agent->systemd_user || ALWAYS_USER_API_BUS) {
150✔
UNCOV
2552
                agent->api_bus = user_bus_open(agent->event);
×
2553
        } else {
2554
                agent->api_bus = system_bus_open(agent->event);
150✔
2555
        }
2556

2557
        if (agent->api_bus == NULL) {
150✔
UNCOV
2558
                bc_log_error("Failed to open api dbus");
×
UNCOV
2559
                return false;
×
2560
        }
2561

2562
        r = sd_bus_add_object_vtable(
150✔
2563
                        agent->api_bus, NULL, BC_AGENT_OBJECT_PATH, AGENT_INTERFACE, agent_vtable, agent);
2564
        if (r < 0) {
150✔
UNCOV
2565
                bc_log_errorf("Failed to add agent vtable: %s", strerror(-r));
×
UNCOV
2566
                return false;
×
2567
        }
2568

2569
        r = sd_bus_request_name(agent->api_bus, agent->api_bus_service_name, SD_BUS_NAME_REPLACE_EXISTING);
150✔
2570
        if (r < 0) {
150✔
UNCOV
2571
                bc_log_errorf("Failed to acquire service name on api dbus: %s", strerror(-r));
×
2572
                return false;
×
2573
        }
2574

2575
        if (agent->systemd_user) {
150✔
UNCOV
2576
                agent->systemd_dbus = user_bus_open(agent->event);
×
2577
        } else {
2578
                agent->systemd_dbus = systemd_bus_open(agent->event);
150✔
2579
        }
2580
        if (agent->systemd_dbus == NULL) {
150✔
UNCOV
2581
                bc_log_error("Failed to open systemd dbus");
×
UNCOV
2582
                return false;
×
2583
        }
2584

2585
        _cleanup_sd_bus_message_ sd_bus_message *sub_m = NULL;
150✔
2586
        r = sd_bus_call_method(
150✔
2587
                        agent->systemd_dbus,
2588
                        SYSTEMD_BUS_NAME,
2589
                        SYSTEMD_OBJECT_PATH,
2590
                        SYSTEMD_MANAGER_IFACE,
2591
                        "Subscribe",
2592
                        &error,
2593
                        &sub_m,
2594
                        "");
2595
        if (r < 0) {
150✔
UNCOV
2596
                bc_log_errorf("Failed to issue subscribe call: %s", error.message);
×
UNCOV
2597
                sd_bus_error_free(&error);
×
2598
                return false;
2599
        }
2600

2601
        r = sd_bus_add_match(
150✔
2602
                        agent->systemd_dbus,
2603
                        NULL,
2604
                        "type='signal',sender='org.freedesktop.systemd1',interface='org.freedesktop.DBus.Properties',member='PropertiesChanged',path_namespace='/org/freedesktop/systemd1/job'",
2605
                        agent_match_job_changed,
2606
                        agent);
2607
        if (r < 0) {
150✔
UNCOV
2608
                bc_log_errorf("Failed to add match: %s", strerror(-r));
×
2609
                return false;
2610
        }
2611

2612
        r = sd_bus_add_match(
150✔
2613
                        agent->systemd_dbus,
2614
                        NULL,
2615
                        "type='signal',sender='org.freedesktop.systemd1',interface='org.freedesktop.DBus.Properties',member='PropertiesChanged',path_namespace='/org/freedesktop/systemd1/unit'",
2616
                        agent_match_unit_changed,
2617
                        agent);
2618
        if (r < 0) {
150✔
UNCOV
2619
                bc_log_errorf("Failed to add match: %s", strerror(-r));
×
2620
                return false;
2621
        }
2622

2623
        r = sd_bus_match_signal(
150✔
2624
                        agent->systemd_dbus,
2625
                        NULL,
2626
                        SYSTEMD_BUS_NAME,
2627
                        SYSTEMD_OBJECT_PATH,
2628
                        SYSTEMD_MANAGER_IFACE,
2629
                        "UnitNew",
2630
                        agent_match_unit_new,
2631
                        agent);
2632
        if (r < 0) {
150✔
UNCOV
2633
                bc_log_errorf("Failed to add unit-new peer bus match: %s", strerror(-r));
×
2634
                return false;
2635
        }
2636

2637
        r = sd_bus_match_signal(
150✔
2638
                        agent->systemd_dbus,
2639
                        NULL,
2640
                        SYSTEMD_BUS_NAME,
2641
                        SYSTEMD_OBJECT_PATH,
2642
                        SYSTEMD_MANAGER_IFACE,
2643
                        "UnitRemoved",
2644
                        agent_match_unit_removed,
2645
                        agent);
2646
        if (r < 0) {
150✔
UNCOV
2647
                bc_log_errorf("Failed to add unit-removed peer bus match: %s", strerror(-r));
×
2648
                return false;
2649
        }
2650

2651
        r = sd_bus_match_signal(
150✔
2652
                        agent->systemd_dbus,
2653
                        NULL,
2654
                        SYSTEMD_BUS_NAME,
2655
                        SYSTEMD_OBJECT_PATH,
2656
                        SYSTEMD_MANAGER_IFACE,
2657
                        "JobRemoved",
2658
                        agent_match_job_removed,
2659
                        agent);
2660
        if (r < 0) {
150✔
UNCOV
2661
                bc_log_errorf("Failed to add job-removed peer bus match: %s", strerror(-r));
×
2662
                return false;
2663
        }
2664

2665
        _cleanup_sd_bus_message_ sd_bus_message *list_units_m = NULL;
150✔
2666
        r = sd_bus_call_method(
150✔
2667
                        agent->systemd_dbus,
2668
                        SYSTEMD_BUS_NAME,
2669
                        SYSTEMD_OBJECT_PATH,
2670
                        SYSTEMD_MANAGER_IFACE,
2671
                        "ListUnits",
2672
                        &error,
2673
                        &list_units_m,
2674
                        "");
2675
        if (r < 0) {
150✔
UNCOV
2676
                bc_log_errorf("Failed to issue list_units call: %s", error.message);
×
UNCOV
2677
                sd_bus_error_free(&error);
×
2678
                return false;
2679
        }
2680

2681
        r = agent_init_units(agent, list_units_m);
150✔
2682
        if (r < 0) {
150✔
2683
                return false;
2684
        }
2685

2686
        if (DEBUG_SYSTEMD_MESSAGES) {
150✔
2687
                sd_bus_add_filter(agent->systemd_dbus, NULL, debug_systemd_message_handler, agent);
2688
        }
2689

2690
        ShutdownHook hook;
150✔
2691
        hook.shutdown = (ShutdownHookFn) agent_stop;
150✔
2692
        hook.userdata = agent;
150✔
2693
        r = event_loop_add_shutdown_signals(agent->event, &hook);
150✔
2694
        if (r < 0) {
150✔
UNCOV
2695
                bc_log_errorf("Failed to add signals to agent event loop: %s", strerror(-r));
×
2696
                return false;
2697
        }
2698

2699
        r = agent_setup_heartbeat_timer(agent);
150✔
2700
        if (r < 0) {
150✔
UNCOV
2701
                bc_log_errorf("Failed to set up agent heartbeat timer: %s", strerror(-r));
×
2702
                return false;
2703
        }
2704

2705
        if (!agent_connect(agent)) {
150✔
UNCOV
2706
                bc_log_error("Initial controller connection failed, retrying");
×
UNCOV
2707
                agent->connection_state = AGENT_CONNECTION_STATE_RETRY;
×
2708
        }
2709

2710
        r = sd_event_loop(agent->event);
150✔
2711
        if (r < 0) {
150✔
UNCOV
2712
                bc_log_errorf("Starting event loop failed: %s", strerror(-r));
×
2713
                return false;
2714
        }
2715

2716
        return true;
2717
}
2718

2719
void agent_stop(Agent *agent) {
150✔
2720
        if (agent == NULL) {
150✔
2721
                return;
2722
        }
2723

2724
        bc_log_debug("Stopping agent");
150✔
2725

2726
        agent_peer_bus_close(agent);
150✔
2727
        agent->connection_state = AGENT_CONNECTION_STATE_DISCONNECTED;
150✔
2728
        int r = sd_bus_emit_properties_changed(
150✔
2729
                        agent->api_bus, BC_AGENT_OBJECT_PATH, AGENT_INTERFACE, "Status", NULL);
2730
        if (r < 0) {
150✔
UNCOV
2731
                bc_log_errorf("Failed to emit status property changed: %s", strerror(-r));
×
2732
        }
2733

2734
        ProxyService *proxy = NULL;
150✔
2735
        ProxyService *next_proxy = NULL;
150✔
2736
        LIST_FOREACH_SAFE(proxy_services, proxy, next_proxy, agent->proxy_services) {
152✔
2737
                agent_remove_proxy(agent, proxy, false);
2✔
2738
        }
2739
}
2740

2741
static bool agent_process_register_callback(sd_bus_message *m, Agent *agent) {
167✔
2742
        int r = 0;
167✔
2743

2744
        if (sd_bus_message_is_method_error(m, NULL)) {
167✔
2745
                bc_log_errorf("Registering as '%s' failed: %s",
16✔
2746
                              agent->name,
2747
                              sd_bus_message_get_error(m)->message);
2748
                return -EPERM;
16✔
2749
        }
2750

2751
        bc_log_info("Register call response received");
151✔
2752
        r = sd_bus_message_read(m, "");
151✔
2753
        if (r < 0) {
151✔
UNCOV
2754
                bc_log_errorf("Failed to parse response message: %s", strerror(-r));
×
UNCOV
2755
                return false;
×
2756
        }
2757

2758
        /* Restore is_quiet setting if it has been disabled during reconnecting */
2759
        bc_log_set_quiet(cfg_get_bool_value(agent->config, CFG_LOG_IS_QUIET));
151✔
2760

2761
        bc_log_infof("Connected to controller as '%s'", agent->name);
151✔
2762

2763
        agent->connection_state = AGENT_CONNECTION_STATE_CONNECTED;
151✔
2764
        agent->connection_retry_count = 0;
151✔
2765
        agent->controller_last_seen = get_time_micros();
151✔
2766
        agent->controller_last_seen_monotonic = get_time_micros_monotonic();
151✔
2767
        agent->disconnect_timestamp = 0;
151✔
2768
        agent->disconnect_timestamp_monotonic = 0;
151✔
2769

2770
        r = sd_bus_emit_properties_changed(
151✔
2771
                        agent->api_bus, BC_AGENT_OBJECT_PATH, AGENT_INTERFACE, "Status", NULL);
2772
        if (r < 0) {
151✔
UNCOV
2773
                bc_log_errorf("Failed to emit status property changed: %s", strerror(-r));
×
2774
        }
2775

2776
        r = sd_bus_match_signal(
151✔
2777
                        agent->peer_dbus,
2778
                        NULL,
2779
                        NULL,
2780
                        INTERNAL_CONTROLLER_OBJECT_PATH,
2781
                        INTERNAL_CONTROLLER_INTERFACE,
2782
                        CONTROLLER_HEARTBEAT_SIGNAL_NAME,
2783
                        agent_match_heartbeat,
2784
                        agent);
2785
        if (r < 0) {
151✔
UNCOV
2786
                bc_log_errorf("Failed to add heartbeat signal match: %s", strerror(-r));
×
UNCOV
2787
                return false;
×
2788
        }
2789

2790
        r = sd_bus_match_signal_async(
151✔
2791
                        agent->peer_dbus,
2792
                        NULL,
2793
                        "org.freedesktop.DBus.Local",
2794
                        "/org/freedesktop/DBus/Local",
2795
                        "org.freedesktop.DBus.Local",
2796
                        "Disconnected",
2797
                        agent_disconnected,
2798
                        NULL,
2799
                        agent);
2800
        if (r < 0) {
151✔
2801
                bc_log_errorf("Failed to request match for Disconnected signal: %s", strerror(-r));
×
2802
                return false;
×
2803
        }
2804

2805
        /* re-emit ProxyNew signals */
2806
        ProxyService *proxy = NULL;
151✔
2807
        ProxyService *next_proxy = NULL;
151✔
2808
        LIST_FOREACH_SAFE(proxy_services, proxy, next_proxy, agent->proxy_services) {
151✔
UNCOV
2809
                r = proxy_service_emit_proxy_new(proxy);
×
UNCOV
2810
                if (r < 0) {
×
UNCOV
2811
                        bc_log_errorf("Failed to re-emit ProxyNew signal for proxy %u requesting unit %s on %s",
×
2812
                                      proxy->id,
2813
                                      proxy->unit_name,
2814
                                      proxy->node_name);
2815
                }
2816
        }
2817

2818
        return true;
2819
}
2820

2821
static int agent_register_callback(sd_bus_message *m, void *userdata, UNUSED sd_bus_error *ret_error) {
167✔
2822
        Agent *agent = (Agent *) userdata;
167✔
2823

2824
        if (agent->connection_state != AGENT_CONNECTION_STATE_CONNECTING) {
167✔
UNCOV
2825
                bc_log_error("Agent is not in CONNECTING state, dropping Register callback");
×
UNCOV
2826
                sd_bus_slot_unrefp(&agent->register_call_slot);
×
UNCOV
2827
                agent->register_call_slot = NULL;
×
UNCOV
2828
                return 0;
×
2829
        }
2830

2831
        if (!agent_process_register_callback(m, agent)) {
167✔
UNCOV
2832
                agent->connection_state = AGENT_CONNECTION_STATE_RETRY;
×
2833
        }
2834

2835
        return 0;
2836
}
2837

2838
static bool agent_connect(Agent *agent) {
174✔
2839
        bc_log_infof("Connecting to controller on %s", agent->assembled_controller_address);
174✔
2840
        agent->connection_state = AGENT_CONNECTION_STATE_CONNECTING;
174✔
2841

2842
        agent->peer_dbus = peer_bus_open(
348✔
2843
                        agent->event, "peer-bus-to-controller", agent->assembled_controller_address);
174✔
2844
        if (agent->peer_dbus == NULL) {
174✔
UNCOV
2845
                bc_log_error("Failed to open peer dbus");
×
UNCOV
2846
                return false;
×
2847
        }
2848

2849
        bus_socket_set_options(agent->peer_dbus, agent->peer_socket_options);
174✔
2850

2851
        int r = sd_bus_add_object_vtable(
174✔
2852
                        agent->peer_dbus,
2853
                        NULL,
2854
                        INTERNAL_AGENT_OBJECT_PATH,
2855
                        INTERNAL_AGENT_INTERFACE,
2856
                        internal_agent_vtable,
2857
                        agent);
2858
        if (r < 0) {
174✔
UNCOV
2859
                bc_log_errorf("Failed to add agent vtable: %s", strerror(-r));
×
UNCOV
2860
                return false;
×
2861
        }
2862

2863
        _cleanup_sd_bus_message_ sd_bus_message *bus_msg = NULL;
174✔
2864
        r = sd_bus_call_method_async(
174✔
2865
                        agent->peer_dbus,
2866
                        &agent->register_call_slot,
2867
                        BC_DBUS_NAME,
2868
                        INTERNAL_CONTROLLER_OBJECT_PATH,
2869
                        INTERNAL_CONTROLLER_INTERFACE,
2870
                        "Register",
2871
                        agent_register_callback,
2872
                        agent,
2873
                        "s",
2874
                        agent->name);
2875
        if (r < 0) {
174✔
UNCOV
2876
                bc_log_errorf("Registering as '%s' failed: %s", agent->name, strerror(-r));
×
UNCOV
2877
                return false;
×
2878
        }
2879

2880
        return true;
2881
}
2882

2883
static bool agent_reconnect(Agent *agent) {
24✔
2884
        _cleanup_free_ char *assembled_controller_address = NULL;
24✔
2885

2886
        // resolve FQDN again in case the system changed
2887
        // e.g. bluechi controller has been migrated to a different host
2888
        if (agent->assembled_controller_address != NULL) {
24✔
2889
                assembled_controller_address = steal_pointer(&agent->assembled_controller_address);
24✔
2890
        }
2891
        if (!ensure_assembled_controller_address(agent)) {
24✔
2892
                return false;
2893
        }
2894

2895
        // If the controller address has changed, emit the respective signal
2896
        if (agent->assembled_controller_address && assembled_controller_address &&
24✔
2897
            !streq(agent->assembled_controller_address, assembled_controller_address)) {
24✔
UNCOV
2898
                int r = sd_bus_emit_properties_changed(
×
2899
                                agent->api_bus, BC_AGENT_OBJECT_PATH, AGENT_INTERFACE, "ControllerAddress", NULL);
UNCOV
2900
                if (r < 0) {
×
UNCOV
2901
                        bc_log_errorf("Failed to emit controller address property changed: %s", strerror(-r));
×
2902
                }
2903
        }
2904

2905
        agent_peer_bus_close(agent);
24✔
2906
        return agent_connect(agent);
24✔
2907
}
2908

2909
static void agent_peer_bus_close(Agent *agent) {
174✔
2910
        if (agent->register_call_slot != NULL) {
174✔
2911
                sd_bus_slot_unref(agent->register_call_slot);
167✔
2912
                agent->register_call_slot = NULL;
167✔
2913
        }
2914
        peer_bus_close(agent->peer_dbus);
174✔
2915
        agent->peer_dbus = NULL;
174✔
2916
}
174✔
2917

2918
static int stop_proxy_callback(sd_bus_message *m, void *userdata, UNUSED sd_bus_error *ret_error) {
3✔
2919
        UNUSED _cleanup_systemd_request_ SystemdRequest *req = userdata;
6✔
2920

2921
        if (sd_bus_message_is_method_error(m, NULL)) {
3✔
UNCOV
2922
                bc_log_errorf("Error stopping proxy service: %s", sd_bus_message_get_error(m)->message);
×
2923
        }
2924

2925
        return 0;
6✔
2926
}
2927

2928
/* Force stop the service via systemd */
2929
static int agent_stop_local_proxy_service(Agent *agent, ProxyService *proxy) {
10✔
2930
        if (proxy->dont_stop_proxy) {
10✔
2931
                return 0;
10✔
2932
        }
2933

2934
        bc_log_infof("Stopping proxy service %s", proxy->local_service_name);
5✔
2935

2936
        /* Don't stop twice */
2937
        proxy->dont_stop_proxy = true;
5✔
2938
        _cleanup_systemd_request_ SystemdRequest *req = agent_create_request(agent, NULL, "StopUnit");
15✔
2939
        if (req == NULL) {
5✔
2940
                return -ENOMEM;
2941
        }
2942

2943
        int r = sd_bus_message_append(req->message, "ss", proxy->local_service_name, "replace");
5✔
2944
        if (r < 0) {
5✔
2945
                return -ENOMEM;
2946
        }
2947

2948
        if (!systemd_request_start(req, stop_proxy_callback)) {
5✔
UNCOV
2949
                return -EIO;
×
2950
        }
2951

2952

2953
        return 0;
2954
}
2955

2956
int agent_send_job_metrics(Agent *agent, char *unit, char *method, uint64_t systemd_job_time) {
5✔
2957
        bc_log_debugf("Sending agent %s job metrics on unit %s: %ldus", unit, method, systemd_job_time);
5✔
2958
        int r = sd_bus_emit_signal(
5✔
2959
                        agent->peer_dbus,
2960
                        INTERNAL_AGENT_METRICS_OBJECT_PATH,
2961
                        INTERNAL_AGENT_METRICS_INTERFACE,
2962
                        "AgentJobMetrics",
2963
                        "sst",
2964
                        unit,
2965
                        method,
2966
                        systemd_job_time);
2967
        if (r < 0) {
5✔
UNCOV
2968
                bc_log_errorf("Failed to emit metric signal: %s", strerror(-r));
×
2969
        }
2970

2971
        return 0;
5✔
2972
}
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