]> git.ipfire.org Git - thirdparty/systemd.git/blame - src/systemctl/systemctl-edit.c
Two follow-ups for recent PRs (#38062)
[thirdparty/systemd.git] / src / systemctl / systemctl-edit.c
CommitLineData
db9ecf05 1/* SPDX-License-Identifier: LGPL-2.1-or-later */
daf71ef6 2
0bbd8e18
DDM
3#include <unistd.h>
4
b78d73fa 5#include "alloc-util.h"
0bbd8e18 6#include "bus-util.h"
a01c4bc9 7#include "edit-util.h"
0bbd8e18
DDM
8#include "hashmap.h"
9#include "label-util.h"
daf71ef6 10#include "pager.h"
0bbd8e18 11#include "path-lookup.h"
daf71ef6
LP
12#include "path-util.h"
13#include "pretty-print.h"
0bbd8e18
DDM
14#include "string-util.h"
15#include "strv.h"
1cf40697 16#include "systemctl.h"
daf71ef6
LP
17#include "systemctl-daemon-reload.h"
18#include "systemctl-edit.h"
19#include "systemctl-util.h"
daf71ef6 20#include "terminal-util.h"
0bbd8e18 21#include "unit-name.h"
daf71ef6 22
32baf64d 23int verb_cat(int argc, char *argv[], void *userdata) {
2962a508 24 _cleanup_hashmap_free_ Hashmap *cached_id_map = NULL, *cached_name_map = NULL;
7dfc7139 25 _cleanup_(lookup_paths_done) LookupPaths lp = {};
daf71ef6 26 _cleanup_strv_free_ char **names = NULL;
daf71ef6
LP
27 sd_bus *bus;
28 bool first = true;
29 int r, rc = 0;
30
31 /* Include all units by default — i.e. continue as if the --all option was used */
32 if (strv_isempty(arg_states))
33 arg_all = true;
34
35 if (arg_transport != BUS_TRANSPORT_LOCAL)
36 return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Cannot remotely cat units.");
37
4870133b 38 r = lookup_paths_init_or_warn(&lp, arg_runtime_scope, 0, arg_root);
daf71ef6 39 if (r < 0)
99aad9a2 40 return r;
daf71ef6
LP
41
42 r = acquire_bus(BUS_MANAGER, &bus);
43 if (r < 0)
44 return r;
45
46 r = expand_unit_names(bus, strv_skip(argv, 1), NULL, &names, NULL);
47 if (r < 0)
48 return log_error_errno(r, "Failed to expand names: %m");
49
50 r = maybe_extend_with_unit_dependencies(bus, &names);
51 if (r < 0)
52 return r;
53
384c2c32 54 pager_open(arg_pager_flags);
daf71ef6
LP
55
56 STRV_FOREACH(name, names) {
57 _cleanup_free_ char *fragment_path = NULL;
58 _cleanup_strv_free_ char **dropin_paths = NULL;
59
2962a508 60 r = unit_find_paths(bus, *name, &lp, false, &cached_id_map, &cached_name_map, &fragment_path, &dropin_paths);
daf71ef6
LP
61 if (r == -ERFKILL) {
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) {
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)
76 return r;
77 if (r == 0) {
78 /* Skip units which have no on-disk counterpart, but propagate the error to the
b498f250
IS
79 * user (if --force is set, eat the error, just like unit_find_paths()) */
80 if (!arg_force)
81 rc = -ENOENT;
daf71ef6
LP
82 continue;
83 }
84
85 if (first)
86 first = false;
87 else
88 puts("");
89
90 if (need_daemon_reload(bus, *name) > 0) /* ignore errors (<0), this is informational output */
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(),
4870133b 101 arg_runtime_scope == RUNTIME_SCOPE_SYSTEM ? "" : " --user",
daf71ef6
LP
102 ansi_normal());
103
063c8382 104 r = cat_files(fragment_path, dropin_paths, /* flags= */ CAT_FORMAT_HAS_SECTIONS);
daf71ef6
LP
105 if (r < 0)
106 return r;
107 }
108
109 return rc;
110}
111
daf71ef6 112static int get_file_to_edit(
90c462bd 113 const LookupPaths *lp,
daf71ef6
LP
114 const char *name,
115 char **ret_path) {
116
90c462bd 117 _cleanup_free_ char *path = NULL;
daf71ef6 118
90c462bd 119 assert(lp);
daf71ef6
LP
120 assert(name);
121 assert(ret_path);
122
90c462bd 123 path = path_join(lp->persistent_config, name);
daf71ef6
LP
124 if (!path)
125 return log_oom();
126
127 if (arg_runtime) {
90c462bd
MY
128 _cleanup_free_ char *run = NULL;
129
130 run = path_join(lp->runtime_config, name);
daf71ef6
LP
131 if (!run)
132 return log_oom();
daf71ef6 133
daf71ef6
LP
134 if (access(path, F_OK) >= 0)
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);
140 } else
141 *ret_path = TAKE_PTR(path);
142
143 return 0;
144}
145
146static int unit_file_create_new(
9a11b4f9 147 EditFileContext *context,
90c462bd 148 const LookupPaths *lp,
daf71ef6
LP
149 const char *unit_name,
150 const char *suffix,
9a11b4f9 151 char * const *original_unit_paths) {
daf71ef6 152
90c462bd 153 _cleanup_free_ char *unit = NULL, *new_path = NULL;
daf71ef6
LP
154 int r;
155
9a11b4f9 156 assert(context);
90c462bd 157 assert(lp);
daf71ef6 158 assert(unit_name);
daf71ef6 159
90c462bd
MY
160 unit = strjoin(unit_name, suffix);
161 if (!unit)
162 return log_oom();
163
164 r = get_file_to_edit(lp, unit, &new_path);
daf71ef6
LP
165 if (r < 0)
166 return r;
167
7a2a7f2c 168 return edit_files_add(context, new_path, NULL, original_unit_paths);
daf71ef6
LP
169}
170
171static int unit_file_create_copy(
9a11b4f9 172 EditFileContext *context,
90c462bd 173 const LookupPaths *lp,
daf71ef6 174 const char *unit_name,
9a11b4f9 175 const char *fragment_path) {
daf71ef6 176
9a11b4f9 177 _cleanup_free_ char *new_path = NULL;
daf71ef6
LP
178 int r;
179
9a11b4f9 180 assert(context);
90c462bd 181 assert(lp);
daf71ef6
LP
182 assert(fragment_path);
183 assert(unit_name);
daf71ef6 184
90c462bd 185 r = get_file_to_edit(lp, unit_name, &new_path);
daf71ef6
LP
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;
90c462bd 195
daf71ef6
LP
196 if (response != 'y')
197 return log_warning_errno(SYNTHETIC_ERRNO(EKEYREJECTED), "%s skipped.", unit_name);
198 }
199
7a2a7f2c 200 return edit_files_add(context, new_path, fragment_path, NULL);
daf71ef6
LP
201}
202
1ae886fe
LP
203static int find_paths_to_edit(
204 sd_bus *bus,
9a11b4f9
MY
205 EditFileContext *context,
206 char **names) {
1ae886fe 207
2962a508 208 _cleanup_hashmap_free_ Hashmap *cached_id_map = NULL, *cached_name_map = NULL;
7dfc7139 209 _cleanup_(lookup_paths_done) LookupPaths lp = {};
f206809b
MY
210 _cleanup_free_ char *drop_in_alloc = NULL, *suffix = NULL;
211 const char *drop_in;
daf71ef6
LP
212 int r;
213
9a11b4f9
MY
214 assert(bus);
215 assert(context);
daf71ef6 216 assert(names);
daf71ef6 217
f206809b
MY
218 if (isempty(arg_drop_in))
219 drop_in = "override.conf";
220 else if (!endswith(arg_drop_in, ".conf")) {
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))
230 return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Invalid drop-in file name '%s'.", drop_in);
231
232 suffix = strjoin(".d/", drop_in);
233 if (!suffix)
234 return log_oom();
235
4870133b 236 r = lookup_paths_init(&lp, arg_runtime_scope, 0, arg_root);
daf71ef6
LP
237 if (r < 0)
238 return r;
239
240 STRV_FOREACH(name, names) {
fe5cb7a7 241 _cleanup_free_ char *path = NULL;
85c5d313 242 _cleanup_strv_free_ char **unit_paths = NULL;
daf71ef6 243
2962a508 244 r = unit_find_paths(bus, *name, &lp, /* force_client_side= */ false, &cached_id_map, &cached_name_map, &path, &unit_paths);
daf71ef6
LP
245 if (r == -EKEYREJECTED) {
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. */
90c462bd 248
daf71ef6 249 log_debug_errno(r, "Unit '%s' was not loaded correctly, retrying client-side.", *name);
2962a508 250 r = unit_find_paths(bus, *name, &lp, /* force_client_side= */ true, &cached_id_map, &cached_name_map, &path, &unit_paths);
daf71ef6
LP
251 }
252 if (r == -ERFKILL)
253 return log_error_errno(r, "Unit '%s' masked, cannot edit.", *name);
254 if (r < 0)
90c462bd 255 return r; /* Already logged by unit_find_paths() */
daf71ef6 256
98199724 257 if (!path) {
8451e720
FS
258 if (!arg_force)
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);
daf71ef6
LP
264
265 /* Create a new unit from scratch */
1ae886fe 266 r = unit_file_create_new(
9a11b4f9 267 context,
1ae886fe
LP
268 &lp,
269 *name,
f206809b 270 arg_full ? NULL : suffix,
9a11b4f9 271 NULL);
daf71ef6 272 } else {
d88e1e48
LP
273 _cleanup_free_ char *unit_name = NULL;
274
275 r = path_extract_filename(path, &unit_name);
276 if (r < 0)
277 return log_error_errno(r, "Failed to extract unit name from path '%s': %m", path);
278
daf71ef6
LP
279 /* We follow unit aliases, but we need to propagate the instance */
280 if (unit_name_is_valid(*name, UNIT_NAME_INSTANCE) &&
281 unit_name_is_valid(unit_name, UNIT_NAME_TEMPLATE)) {
fe5cb7a7 282 _cleanup_free_ char *instance = NULL, *tmp_name = NULL;
daf71ef6
LP
283
284 r = unit_name_to_instance(*name, &instance);
285 if (r < 0)
286 return r;
287
288 r = unit_name_replace_instance(unit_name, instance, &tmp_name);
289 if (r < 0)
290 return r;
291
fe5cb7a7 292 free_and_replace(unit_name, tmp_name);
daf71ef6
LP
293 }
294
295 if (arg_full)
1ae886fe 296 r = unit_file_create_copy(
9a11b4f9 297 context,
1ae886fe
LP
298 &lp,
299 unit_name,
9a11b4f9 300 path);
85c5d313 301 else {
302 r = strv_prepend(&unit_paths, path);
303 if (r < 0)
304 return log_oom();
305
1ae886fe 306 r = unit_file_create_new(
9a11b4f9 307 context,
1ae886fe
LP
308 &lp,
309 unit_name,
f206809b 310 suffix,
9a11b4f9 311 unit_paths);
85c5d313 312 }
daf71ef6
LP
313 }
314 if (r < 0)
315 return r;
daf71ef6
LP
316 }
317
318 return 0;
319}
320
32baf64d 321int verb_edit(int argc, char *argv[], void *userdata) {
9a11b4f9 322 _cleanup_(edit_file_context_done) EditFileContext context = {
6e5d0e31
MY
323 .marker_start = DROPIN_MARKER_START,
324 .marker_end = DROPIN_MARKER_END,
9a11b4f9 325 .remove_parent = !arg_full,
bc6c7a58 326 .overwrite_with_origin = true,
79f0e94e 327 .read_from_stdin = arg_stdin,
9a11b4f9 328 };
daf71ef6 329 _cleanup_strv_free_ char **names = NULL;
daf71ef6
LP
330 sd_bus *bus;
331 int r;
332
329050c5 333 if (!on_tty() && !arg_stdin)
119cba78 334 return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Cannot edit units interactively if not on a tty.");
daf71ef6
LP
335
336 if (arg_transport != BUS_TRANSPORT_LOCAL)
337 return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Cannot edit units remotely.");
338
a452c807 339 r = mac_init();
daf71ef6
LP
340 if (r < 0)
341 return r;
342
343 r = acquire_bus(BUS_MANAGER, &bus);
344 if (r < 0)
345 return r;
346
347 r = expand_unit_names(bus, strv_skip(argv, 1), NULL, &names, NULL);
348 if (r < 0)
349 return log_error_errno(r, "Failed to expand names: %m");
ffcd6838
ZJS
350 if (strv_isempty(names))
351 return log_error_errno(SYNTHETIC_ERRNO(ENOENT), "No units matched the specified patterns.");
daf71ef6 352
329050c5
ZJS
353 if (arg_stdin && arg_full && strv_length(names) != 1)
354 return log_error_errno(SYNTHETIC_ERRNO(EINVAL),
355 "With 'edit --stdin --full', exactly one unit for editing must be specified.");
356
daf71ef6 357 STRV_FOREACH(tmp, names) {
6ea32f61 358 r = unit_is_masked(bus, *tmp);
b58b00e4
YW
359 if (r < 0 && r != -ENOENT)
360 return log_error_errno(r, "Failed to check if unit %s is masked: %m", *tmp);
daf71ef6
LP
361 if (r > 0)
362 return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Cannot edit %s: unit is masked.", *tmp);
363 }
364
9a11b4f9 365 r = find_paths_to_edit(bus, &context, names);
daf71ef6
LP
366 if (r < 0)
367 return r;
daf71ef6 368
9a11b4f9 369 r = do_edit_files_and_install(&context);
daf71ef6 370 if (r < 0)
9a11b4f9 371 return r;
daf71ef6 372
623461c1
LP
373 if (!arg_no_reload && !install_client_side()) {
374 r = daemon_reload(ACTION_RELOAD, /* graceful= */ false);
321291d8
YW
375 if (r < 0)
376 return r;
623461c1 377 }
daf71ef6 378
321291d8 379 return 0;
daf71ef6 380}