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

systemd / systemd / 19282013399

12 Nov 2025 12:00AM UTC coverage: 72.412% (+0.01%) from 72.402%
19282013399

push

github

web-flow
core/exec-credentials: port to new mount API, ensure atomicity for creds installation (#39637)

103 of 137 new or added lines in 4 files covered. (75.18%)

850 existing lines in 45 files now uncovered.

307170 of 424195 relevant lines covered (72.41%)

1105108.57 hits per line

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

73.6
/src/systemctl/systemctl-edit.c
1
/* SPDX-License-Identifier: LGPL-2.1-or-later */
2

3
#include <unistd.h>
4

5
#include "alloc-util.h"
6
#include "bus-util.h"
7
#include "edit-util.h"
8
#include "hashmap.h"
9
#include "label-util.h"
10
#include "pager.h"
11
#include "path-lookup.h"
12
#include "path-util.h"
13
#include "pretty-print.h"
14
#include "string-util.h"
15
#include "strv.h"
16
#include "systemctl.h"
17
#include "systemctl-daemon-reload.h"
18
#include "systemctl-edit.h"
19
#include "systemctl-util.h"
20
#include "terminal-util.h"
21
#include "unit-name.h"
22

23
int verb_cat(int argc, char *argv[], void *userdata) {
117✔
24
        _cleanup_hashmap_free_ Hashmap *cached_id_map = NULL, *cached_name_map = NULL;
117✔
25
        _cleanup_(lookup_paths_done) LookupPaths lp = {};
117✔
26
        _cleanup_strv_free_ char **names = NULL;
117✔
27
        sd_bus *bus = NULL;
117✔
28
        bool first = true;
117✔
29
        int r, rc = 0;
117✔
30

31
        /* Include all units by default — i.e. continue as if the --all option was used */
32
        if (strv_isempty(arg_states))
117✔
33
                arg_all = true;
117✔
34

35
        if (arg_transport != BUS_TRANSPORT_LOCAL)
117✔
36
                return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Cannot remotely cat units.");
×
37

38
        r = lookup_paths_init_or_warn(&lp, arg_runtime_scope, 0, arg_root);
117✔
39
        if (r < 0)
117✔
40
                return r;
41

42
        if (install_client_side() == INSTALL_CLIENT_SIDE_NO) {
117✔
43
                r = acquire_bus(BUS_MANAGER, &bus);
114✔
44
                if (r < 0)
114✔
45
                        return r;
46

47
                r = expand_unit_names(bus, strv_skip(argv, 1), NULL, &names, NULL);
114✔
48
                if (r < 0)
114✔
UNCOV
49
                        return log_error_errno(r, "Failed to expand names: %m");
×
50

51
                r = maybe_extend_with_unit_dependencies(bus, &names);
114✔
52
                if (r < 0)
114✔
53
                        return r;
54
        } else {
55
                /* In client-side mode (--global, --root, etc.), just mangle names without bus interaction */
56
                r = mangle_names("to cat", strv_skip(argv, 1), &names);
3✔
57
                if (r < 0)
3✔
58
                        return r;
59
        }
60

61
        pager_open(arg_pager_flags);
117✔
62

63
        STRV_FOREACH(name, names) {
241✔
64
                _cleanup_free_ char *fragment_path = NULL;
124✔
65
                _cleanup_strv_free_ char **dropin_paths = NULL;
124✔
66

67
                r = unit_find_paths(bus, *name, &lp, /* force_client_side= */ !bus, &cached_id_map, &cached_name_map, &fragment_path, &dropin_paths);
124✔
68
                if (r == -ERFKILL) {
124✔
69
                        printf("%s# Unit %s is masked%s.\n",
3✔
70
                               ansi_highlight_magenta(),
71
                               *name,
72
                               ansi_normal());
73
                        continue;
1✔
74
                }
75
                if (r == -EKEYREJECTED) {
123✔
UNCOV
76
                        printf("%s# Unit %s could not be loaded.%s\n",
×
77
                               ansi_highlight_magenta(),
78
                               *name,
79
                               ansi_normal());
UNCOV
80
                        continue;
×
81
                }
82
                if (r < 0)
123✔
83
                        return r;
84
                if (r == 0) {
123✔
85
                        /* Skip units which have no on-disk counterpart, but propagate the error to the
86
                         * user (if --force is set, eat the error, just like unit_find_paths()) */
87
                        if (!arg_force)
6✔
88
                                rc = -ENOENT;
5✔
89
                        continue;
6✔
90
                }
91

92
                if (first)
117✔
93
                        first = false;
94
                else
95
                        puts("");
7✔
96

97
                if (bus && need_daemon_reload(bus, *name) > 0) /* ignore errors (<0), this is informational output */
117✔
UNCOV
98
                        fprintf(stderr,
×
99
                                "%s# Warning: %s changed on disk, the version systemd has loaded is outdated.\n"
100
                                "%s# This output shows the current version of the unit's original fragment and drop-in files.\n"
101
                                "%s# If fragments or drop-ins were added or removed, they are not properly reflected in this output.\n"
102
                                "%s# Run 'systemctl%s daemon-reload' to reload units.%s\n",
103
                                ansi_highlight_red(),
104
                                *name,
105
                                ansi_highlight_red(),
106
                                ansi_highlight_red(),
107
                                ansi_highlight_red(),
UNCOV
108
                                arg_runtime_scope == RUNTIME_SCOPE_SYSTEM ? "" : " --user",
×
109
                                ansi_normal());
110

111
                r = cat_files(fragment_path, dropin_paths, /* flags= */ CAT_FORMAT_HAS_SECTIONS);
117✔
112
                if (r < 0)
117✔
113
                        return r;
114
        }
115

116
        return rc;
117
}
118

119
static int get_file_to_edit(
17✔
120
                const LookupPaths *lp,
121
                const char *name,
122
                char **ret_path) {
123

124
        _cleanup_free_ char *path = NULL;
17✔
125

126
        assert(lp);
17✔
127
        assert(name);
17✔
128
        assert(ret_path);
17✔
129

130
        path = path_join(lp->persistent_config, name);
17✔
131
        if (!path)
17✔
132
                return log_oom();
×
133

134
        if (arg_runtime) {
17✔
135
                _cleanup_free_ char *run = NULL;
×
136

137
                run = path_join(lp->runtime_config, name);
8✔
138
                if (!run)
8✔
UNCOV
139
                        return log_oom();
×
140

141
                if (access(path, F_OK) >= 0)
8✔
UNCOV
142
                        return log_error_errno(SYNTHETIC_ERRNO(EEXIST),
×
143
                                               "Refusing to create \"%s\" because it would be overridden by \"%s\" anyway.",
144
                                               run, path);
145

146
                *ret_path = TAKE_PTR(run);
8✔
147
        } else
148
                *ret_path = TAKE_PTR(path);
9✔
149

150
        return 0;
151
}
152

153
static int unit_file_create_new(
17✔
154
                EditFileContext *context,
155
                const LookupPaths *lp,
156
                const char *unit_name,
157
                const char *suffix,
158
                char * const *original_unit_paths) {
159

160
        _cleanup_free_ char *unit = NULL, *new_path = NULL;
17✔
161
        int r;
17✔
162

163
        assert(context);
17✔
164
        assert(lp);
17✔
165
        assert(unit_name);
17✔
166

167
        unit = strjoin(unit_name, suffix);
17✔
168
        if (!unit)
17✔
UNCOV
169
                return log_oom();
×
170

171
        r = get_file_to_edit(lp, unit, &new_path);
17✔
172
        if (r < 0)
17✔
173
                return r;
174

175
        return edit_files_add(context, new_path, NULL, original_unit_paths);
17✔
176
}
177

178
static int unit_file_create_copy(
×
179
                EditFileContext *context,
180
                const LookupPaths *lp,
181
                const char *unit_name,
182
                const char *fragment_path) {
183

UNCOV
184
        _cleanup_free_ char *new_path = NULL;
×
185
        int r;
×
186

UNCOV
187
        assert(context);
×
UNCOV
188
        assert(lp);
×
189
        assert(fragment_path);
×
190
        assert(unit_name);
×
191

192
        r = get_file_to_edit(lp, unit_name, &new_path);
×
193
        if (r < 0)
×
194
                return r;
195

196
        if (!path_equal(fragment_path, new_path) && access(new_path, F_OK) >= 0) {
×
197
                char response;
×
198

UNCOV
199
                r = ask_char(&response, "yn", "\"%s\" already exists. Overwrite with \"%s\"? [(y)es, (n)o] ", new_path, fragment_path);
×
200
                if (r < 0)
×
UNCOV
201
                        return r;
×
202

UNCOV
203
                if (response != 'y')
×
UNCOV
204
                        return log_warning_errno(SYNTHETIC_ERRNO(EKEYREJECTED), "%s skipped.", unit_name);
×
205
        }
206

UNCOV
207
        return edit_files_add(context, new_path, fragment_path, NULL);
×
208
}
209

210
static int find_paths_to_edit(
17✔
211
                sd_bus *bus,
212
                EditFileContext *context,
213
                char **names) {
214

215
        _cleanup_hashmap_free_ Hashmap *cached_id_map = NULL, *cached_name_map = NULL;
17✔
UNCOV
216
        _cleanup_(lookup_paths_done) LookupPaths lp = {};
×
217
        _cleanup_free_ char *drop_in_alloc = NULL, *suffix = NULL;
17✔
218
        const char *drop_in;
17✔
219
        int r;
17✔
220

221
        assert(context);
17✔
222
        assert(names);
17✔
223

224
        if (isempty(arg_drop_in))
17✔
225
                drop_in = "override.conf";
226
        else if (!endswith(arg_drop_in, ".conf")) {
8✔
UNCOV
227
                drop_in_alloc = strjoin(arg_drop_in, ".conf");
×
UNCOV
228
                if (!drop_in_alloc)
×
UNCOV
229
                        return log_oom();
×
230

231
                drop_in = drop_in_alloc;
232
        } else
233
                drop_in = arg_drop_in;
234

235
        if (!filename_is_valid(drop_in))
17✔
UNCOV
236
                return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Invalid drop-in file name '%s'.", drop_in);
×
237

238
        suffix = strjoin(".d/", drop_in);
17✔
239
        if (!suffix)
17✔
UNCOV
240
                return log_oom();
×
241

242
        r = lookup_paths_init(&lp, arg_runtime_scope, 0, arg_root);
17✔
243
        if (r < 0)
17✔
244
                return r;
245

246
        STRV_FOREACH(name, names) {
34✔
247
                _cleanup_free_ char *path = NULL;
18✔
248
                _cleanup_strv_free_ char **unit_paths = NULL;
18✔
249

250
                r = unit_find_paths(bus, *name, &lp, /* force_client_side= */ !bus, &cached_id_map, &cached_name_map, &path, &unit_paths);
18✔
251
                if (r == -EKEYREJECTED && bus) {
18✔
252
                        /* If loading of the unit failed server side complete, then the server won't tell us
253
                         * the unit file path. In that case, find the file client side. */
254

UNCOV
255
                        log_debug_errno(r, "Unit '%s' was not loaded correctly, retrying client-side.", *name);
×
UNCOV
256
                        r = unit_find_paths(bus, *name, &lp, /* force_client_side= */ true, &cached_id_map, &cached_name_map, &path, &unit_paths);
×
257
                }
258
                if (r == -ERFKILL)
18✔
259
                        return log_error_errno(r, "Unit '%s' masked, cannot edit.", *name);
1✔
260
                if (r < 0)
17✔
261
                        return r; /* Already logged by unit_find_paths() */
262

263
                if (!path) {
17✔
264
                        if (!arg_force)
4✔
UNCOV
265
                                return log_info_errno(SYNTHETIC_ERRNO(ENOENT),
×
266
                                                      "Run 'systemctl edit%s --force --full %s' to create a new unit.",
267
                                                      arg_runtime_scope == RUNTIME_SCOPE_GLOBAL ? " --global" :
268
                                                      arg_runtime_scope == RUNTIME_SCOPE_USER ? " --user" : "",
269
                                                      *name);
270

271
                        /* Create a new unit from scratch */
272
                        r = unit_file_create_new(
5✔
273
                                        context,
274
                                        &lp,
275
                                        *name,
276
                                        arg_full ? NULL : suffix,
277
                                        NULL);
278
                } else {
279
                        _cleanup_free_ char *unit_name = NULL;
13✔
280

281
                        r = path_extract_filename(path, &unit_name);
13✔
282
                        if (r < 0)
13✔
UNCOV
283
                                return log_error_errno(r, "Failed to extract unit name from path '%s': %m", path);
×
284

285
                        /* We follow unit aliases, but we need to propagate the instance */
286
                        if (unit_name_is_valid(*name, UNIT_NAME_INSTANCE) &&
14✔
287
                            unit_name_is_valid(unit_name, UNIT_NAME_TEMPLATE)) {
1✔
UNCOV
288
                                _cleanup_free_ char *instance = NULL, *tmp_name = NULL;
×
289

290
                                r = unit_name_to_instance(*name, &instance);
1✔
291
                                if (r < 0)
1✔
292
                                        return r;
293

294
                                r = unit_name_replace_instance(unit_name, instance, &tmp_name);
1✔
295
                                if (r < 0)
1✔
296
                                        return r;
297

298
                                free_and_replace(unit_name, tmp_name);
1✔
299
                        }
300

301
                        if (arg_full)
13✔
UNCOV
302
                                r = unit_file_create_copy(
×
303
                                                context,
304
                                                &lp,
305
                                                unit_name,
306
                                                path);
307
                        else {
308
                                r = strv_prepend(&unit_paths, path);
13✔
309
                                if (r < 0)
13✔
UNCOV
310
                                        return log_oom();
×
311

312
                                r = unit_file_create_new(
13✔
313
                                                context,
314
                                                &lp,
315
                                                unit_name,
316
                                                suffix,
317
                                                unit_paths);
318
                        }
319
                }
320
                if (r < 0)
17✔
321
                        return r;
322
        }
323

324
        return 0;
325
}
326

327
int verb_edit(int argc, char *argv[], void *userdata) {
17✔
328
        _cleanup_(edit_file_context_done) EditFileContext context = {
17✔
329
                .marker_start = DROPIN_MARKER_START,
330
                .marker_end = DROPIN_MARKER_END,
331
                .remove_parent = !arg_full,
17✔
332
                .overwrite_with_origin = true,
333
                .read_from_stdin = arg_stdin,
334
        };
335
        _cleanup_strv_free_ char **names = NULL;
17✔
336
        sd_bus *bus = NULL;
17✔
337
        int r;
17✔
338

339
        if (!on_tty() && !arg_stdin)
17✔
UNCOV
340
                return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Cannot edit units interactively if not on a tty.");
×
341

342
        if (arg_transport != BUS_TRANSPORT_LOCAL)
17✔
UNCOV
343
                return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Cannot edit units remotely.");
×
344

345
        r = mac_init();
17✔
346
        if (r < 0)
17✔
347
                return r;
348

349
        if (install_client_side() == INSTALL_CLIENT_SIDE_NO) {
17✔
350
                r = acquire_bus(BUS_MANAGER, &bus);
14✔
351
                if (r < 0)
14✔
352
                        return r;
353

354
                r = expand_unit_names(bus, strv_skip(argv, 1), NULL, &names, NULL);
14✔
355
                if (r < 0)
14✔
UNCOV
356
                        return log_error_errno(r, "Failed to expand names: %m");
×
357
        } else {
358
                /* In client-side mode (--global, --root, etc.), just mangle names without bus interaction */
359
                r = mangle_names("to edit", strv_skip(argv, 1), &names);
3✔
360
                if (r < 0)
3✔
361
                        return r;
362
        }
363
        if (strv_isempty(names))
17✔
UNCOV
364
                return log_error_errno(SYNTHETIC_ERRNO(ENOENT), "No units matched the specified patterns.");
×
365

366
        if (arg_stdin && arg_full && strv_length(names) != 1)
17✔
UNCOV
367
                return log_error_errno(SYNTHETIC_ERRNO(EINVAL),
×
368
                                       "With 'edit --stdin --full', exactly one unit for editing must be specified.");
369

370
        r = find_paths_to_edit(bus, &context, names);
17✔
371
        if (r < 0)
17✔
372
                return r;
373

374
        r = do_edit_files_and_install(&context);
16✔
375
        if (r < 0)
16✔
376
                return r;
377

378
        if (!arg_no_reload &&
32✔
379
            install_client_side() == INSTALL_CLIENT_SIDE_NO) {
16✔
380
                r = daemon_reload(ACTION_RELOAD, /* graceful= */ false);
14✔
381
                if (r < 0)
14✔
UNCOV
382
                        return r;
×
383
        }
384

385
        return 0;
386
}
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