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

systemd / systemd / 19020191358

02 Nov 2025 05:04PM UTC coverage: 72.222% (-0.02%) from 72.241%
19020191358

push

github

web-flow
Enhance docs for ukify and direct kernel boots (#39516)

305246 of 422650 relevant lines covered (72.22%)

1085243.28 hits per line

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

70.95
/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) {
114✔
24
        _cleanup_hashmap_free_ Hashmap *cached_id_map = NULL, *cached_name_map = NULL;
114✔
25
        _cleanup_(lookup_paths_done) LookupPaths lp = {};
114✔
26
        _cleanup_strv_free_ char **names = NULL;
114✔
27
        sd_bus *bus;
114✔
28
        bool first = true;
114✔
29
        int r, rc = 0;
114✔
30

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

35
        if (arg_transport != BUS_TRANSPORT_LOCAL)
114✔
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);
114✔
39
        if (r < 0)
114✔
40
                return r;
41

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

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

50
        r = maybe_extend_with_unit_dependencies(bus, &names);
114✔
51
        if (r < 0)
114✔
52
                return r;
53

54
        pager_open(arg_pager_flags);
114✔
55

56
        STRV_FOREACH(name, names) {
235✔
57
                _cleanup_free_ char *fragment_path = NULL;
121✔
58
                _cleanup_strv_free_ char **dropin_paths = NULL;
121✔
59

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

85
                if (first)
115✔
86
                        first = false;
87
                else
88
                        puts("");
7✔
89

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

104
                r = cat_files(fragment_path, dropin_paths, /* flags= */ CAT_FORMAT_HAS_SECTIONS);
115✔
105
                if (r < 0)
115✔
106
                        return r;
107
        }
108

109
        return rc;
110
}
111

112
static int get_file_to_edit(
15✔
113
                const LookupPaths *lp,
114
                const char *name,
115
                char **ret_path) {
116

117
        _cleanup_free_ char *path = NULL;
15✔
118

119
        assert(lp);
15✔
120
        assert(name);
15✔
121
        assert(ret_path);
15✔
122

123
        path = path_join(lp->persistent_config, name);
15✔
124
        if (!path)
15✔
125
                return log_oom();
×
126

127
        if (arg_runtime) {
15✔
128
                _cleanup_free_ char *run = NULL;
×
129

130
                run = path_join(lp->runtime_config, name);
6✔
131
                if (!run)
6✔
132
                        return log_oom();
×
133

134
                if (access(path, F_OK) >= 0)
6✔
135
                        return log_error_errno(SYNTHETIC_ERRNO(EEXIST),
×
136
                                               "Refusing to create \"%s\" because it would be overridden by \"%s\" anyway.",
137
                                               run, path);
138

139
                *ret_path = TAKE_PTR(run);
6✔
140
        } else
141
                *ret_path = TAKE_PTR(path);
9✔
142

143
        return 0;
144
}
145

146
static int unit_file_create_new(
15✔
147
                EditFileContext *context,
148
                const LookupPaths *lp,
149
                const char *unit_name,
150
                const char *suffix,
151
                char * const *original_unit_paths) {
152

153
        _cleanup_free_ char *unit = NULL, *new_path = NULL;
15✔
154
        int r;
15✔
155

156
        assert(context);
15✔
157
        assert(lp);
15✔
158
        assert(unit_name);
15✔
159

160
        unit = strjoin(unit_name, suffix);
15✔
161
        if (!unit)
15✔
162
                return log_oom();
×
163

164
        r = get_file_to_edit(lp, unit, &new_path);
15✔
165
        if (r < 0)
15✔
166
                return r;
167

168
        return edit_files_add(context, new_path, NULL, original_unit_paths);
15✔
169
}
170

171
static int unit_file_create_copy(
×
172
                EditFileContext *context,
173
                const LookupPaths *lp,
174
                const char *unit_name,
175
                const char *fragment_path) {
176

177
        _cleanup_free_ char *new_path = NULL;
×
178
        int r;
×
179

180
        assert(context);
×
181
        assert(lp);
×
182
        assert(fragment_path);
×
183
        assert(unit_name);
×
184

185
        r = get_file_to_edit(lp, unit_name, &new_path);
×
186
        if (r < 0)
×
187
                return r;
188

189
        if (!path_equal(fragment_path, new_path) && access(new_path, F_OK) >= 0) {
×
190
                char response;
×
191

192
                r = ask_char(&response, "yn", "\"%s\" already exists. Overwrite with \"%s\"? [(y)es, (n)o] ", new_path, fragment_path);
×
193
                if (r < 0)
×
194
                        return r;
×
195

196
                if (response != 'y')
×
197
                        return log_warning_errno(SYNTHETIC_ERRNO(EKEYREJECTED), "%s skipped.", unit_name);
×
198
        }
199

200
        return edit_files_add(context, new_path, fragment_path, NULL);
×
201
}
202

203
static int find_paths_to_edit(
14✔
204
                sd_bus *bus,
205
                EditFileContext *context,
206
                char **names) {
207

208
        _cleanup_hashmap_free_ Hashmap *cached_id_map = NULL, *cached_name_map = NULL;
14✔
209
        _cleanup_(lookup_paths_done) LookupPaths lp = {};
×
210
        _cleanup_free_ char *drop_in_alloc = NULL, *suffix = NULL;
14✔
211
        const char *drop_in;
14✔
212
        int r;
14✔
213

214
        assert(bus);
14✔
215
        assert(context);
14✔
216
        assert(names);
14✔
217

218
        if (isempty(arg_drop_in))
14✔
219
                drop_in = "override.conf";
220
        else if (!endswith(arg_drop_in, ".conf")) {
8✔
221
                drop_in_alloc = strjoin(arg_drop_in, ".conf");
×
222
                if (!drop_in_alloc)
×
223
                        return log_oom();
×
224

225
                drop_in = drop_in_alloc;
226
        } else
227
                drop_in = arg_drop_in;
228

229
        if (!filename_is_valid(drop_in))
14✔
230
                return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Invalid drop-in file name '%s'.", drop_in);
×
231

232
        suffix = strjoin(".d/", drop_in);
14✔
233
        if (!suffix)
14✔
234
                return log_oom();
×
235

236
        r = lookup_paths_init(&lp, arg_runtime_scope, 0, arg_root);
14✔
237
        if (r < 0)
14✔
238
                return r;
239

240
        STRV_FOREACH(name, names) {
29✔
241
                _cleanup_free_ char *path = NULL;
15✔
242
                _cleanup_strv_free_ char **unit_paths = NULL;
15✔
243

244
                r = unit_find_paths(bus, *name, &lp, /* force_client_side= */ false, &cached_id_map, &cached_name_map, &path, &unit_paths);
15✔
245
                if (r == -EKEYREJECTED) {
15✔
246
                        /* If loading of the unit failed server side complete, then the server won't tell us
247
                         * the unit file path. In that case, find the file client side. */
248

249
                        log_debug_errno(r, "Unit '%s' was not loaded correctly, retrying client-side.", *name);
×
250
                        r = unit_find_paths(bus, *name, &lp, /* force_client_side= */ true, &cached_id_map, &cached_name_map, &path, &unit_paths);
×
251
                }
252
                if (r == -ERFKILL)
15✔
253
                        return log_error_errno(r, "Unit '%s' masked, cannot edit.", *name);
×
254
                if (r < 0)
15✔
255
                        return r; /* Already logged by unit_find_paths() */
256

257
                if (!path) {
15✔
258
                        if (!arg_force)
3✔
259
                                return log_info_errno(SYNTHETIC_ERRNO(ENOENT),
×
260
                                                      "Run 'systemctl edit%s --force --full %s' to create a new unit.",
261
                                                      arg_runtime_scope == RUNTIME_SCOPE_GLOBAL ? " --global" :
262
                                                      arg_runtime_scope == RUNTIME_SCOPE_USER ? " --user" : "",
263
                                                      *name);
264

265
                        /* Create a new unit from scratch */
266
                        r = unit_file_create_new(
4✔
267
                                        context,
268
                                        &lp,
269
                                        *name,
270
                                        arg_full ? NULL : suffix,
271
                                        NULL);
272
                } else {
273
                        _cleanup_free_ char *unit_name = NULL;
12✔
274

275
                        r = path_extract_filename(path, &unit_name);
12✔
276
                        if (r < 0)
12✔
277
                                return log_error_errno(r, "Failed to extract unit name from path '%s': %m", path);
×
278

279
                        /* We follow unit aliases, but we need to propagate the instance */
280
                        if (unit_name_is_valid(*name, UNIT_NAME_INSTANCE) &&
13✔
281
                            unit_name_is_valid(unit_name, UNIT_NAME_TEMPLATE)) {
1✔
282
                                _cleanup_free_ char *instance = NULL, *tmp_name = NULL;
×
283

284
                                r = unit_name_to_instance(*name, &instance);
1✔
285
                                if (r < 0)
1✔
286
                                        return r;
287

288
                                r = unit_name_replace_instance(unit_name, instance, &tmp_name);
1✔
289
                                if (r < 0)
1✔
290
                                        return r;
291

292
                                free_and_replace(unit_name, tmp_name);
1✔
293
                        }
294

295
                        if (arg_full)
12✔
296
                                r = unit_file_create_copy(
×
297
                                                context,
298
                                                &lp,
299
                                                unit_name,
300
                                                path);
301
                        else {
302
                                r = strv_prepend(&unit_paths, path);
12✔
303
                                if (r < 0)
12✔
304
                                        return log_oom();
×
305

306
                                r = unit_file_create_new(
12✔
307
                                                context,
308
                                                &lp,
309
                                                unit_name,
310
                                                suffix,
311
                                                unit_paths);
312
                        }
313
                }
314
                if (r < 0)
15✔
315
                        return r;
316
        }
317

318
        return 0;
319
}
320

321
int verb_edit(int argc, char *argv[], void *userdata) {
14✔
322
        _cleanup_(edit_file_context_done) EditFileContext context = {
14✔
323
                .marker_start = DROPIN_MARKER_START,
324
                .marker_end = DROPIN_MARKER_END,
325
                .remove_parent = !arg_full,
14✔
326
                .overwrite_with_origin = true,
327
                .read_from_stdin = arg_stdin,
328
        };
329
        _cleanup_strv_free_ char **names = NULL;
14✔
330
        sd_bus *bus;
14✔
331
        int r;
14✔
332

333
        if (!on_tty() && !arg_stdin)
14✔
334
                return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Cannot edit units interactively if not on a tty.");
×
335

336
        if (arg_transport != BUS_TRANSPORT_LOCAL)
14✔
337
                return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Cannot edit units remotely.");
×
338

339
        r = mac_init();
14✔
340
        if (r < 0)
14✔
341
                return r;
342

343
        r = acquire_bus(BUS_MANAGER, &bus);
14✔
344
        if (r < 0)
14✔
345
                return r;
346

347
        r = expand_unit_names(bus, strv_skip(argv, 1), NULL, &names, NULL);
14✔
348
        if (r < 0)
14✔
349
                return log_error_errno(r, "Failed to expand names: %m");
×
350
        if (strv_isempty(names))
14✔
351
                return log_error_errno(SYNTHETIC_ERRNO(ENOENT), "No units matched the specified patterns.");
×
352

353
        if (arg_stdin && arg_full && strv_length(names) != 1)
14✔
354
                return log_error_errno(SYNTHETIC_ERRNO(EINVAL),
×
355
                                       "With 'edit --stdin --full', exactly one unit for editing must be specified.");
356

357
        STRV_FOREACH(tmp, names) {
29✔
358
                r = unit_is_masked(bus, *tmp);
15✔
359
                if (r < 0 && r != -ENOENT)
15✔
360
                        return log_error_errno(r, "Failed to check if unit %s is masked: %m", *tmp);
×
361
                if (r > 0)
15✔
362
                        return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Cannot edit %s: unit is masked.", *tmp);
×
363
        }
364

365
        r = find_paths_to_edit(bus, &context, names);
14✔
366
        if (r < 0)
14✔
367
                return r;
368

369
        r = do_edit_files_and_install(&context);
14✔
370
        if (r < 0)
14✔
371
                return r;
372

373
        if (!arg_no_reload &&
28✔
374
            install_client_side() == INSTALL_CLIENT_SIDE_NO) {
14✔
375
                r = daemon_reload(ACTION_RELOAD, /* graceful= */ false);
14✔
376
                if (r < 0)
14✔
377
                        return r;
×
378
        }
379

380
        return 0;
381
}
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