]> git.ipfire.org Git - thirdparty/systemd.git/blob - src/systemctl/systemctl-edit.c
c851c8546ef55c51caba46780ace0ff5bce9d6d9
[thirdparty/systemd.git] / src / systemctl / systemctl-edit.c
1 /* SPDX-License-Identifier: LGPL-2.1-or-later */
2
3 #include "bus-error.h"
4 #include "edit-util.h"
5 #include "fs-util.h"
6 #include "pager.h"
7 #include "path-util.h"
8 #include "pretty-print.h"
9 #include "selinux-util.h"
10 #include "systemctl-daemon-reload.h"
11 #include "systemctl-edit.h"
12 #include "systemctl-util.h"
13 #include "systemctl.h"
14 #include "terminal-util.h"
15
16 int verb_cat(int argc, char *argv[], void *userdata) {
17 _cleanup_hashmap_free_ Hashmap *cached_id_map = NULL, *cached_name_map = NULL;
18 _cleanup_(lookup_paths_free) LookupPaths lp = {};
19 _cleanup_strv_free_ char **names = NULL;
20 sd_bus *bus;
21 bool first = true;
22 int r, rc = 0;
23
24 /* Include all units by default — i.e. continue as if the --all option was used */
25 if (strv_isempty(arg_states))
26 arg_all = true;
27
28 if (arg_transport != BUS_TRANSPORT_LOCAL)
29 return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Cannot remotely cat units.");
30
31 r = lookup_paths_init_or_warn(&lp, arg_runtime_scope, 0, arg_root);
32 if (r < 0)
33 return r;
34
35 r = acquire_bus(BUS_MANAGER, &bus);
36 if (r < 0)
37 return r;
38
39 r = expand_unit_names(bus, strv_skip(argv, 1), NULL, &names, NULL);
40 if (r < 0)
41 return log_error_errno(r, "Failed to expand names: %m");
42
43 r = maybe_extend_with_unit_dependencies(bus, &names);
44 if (r < 0)
45 return r;
46
47 pager_open(arg_pager_flags);
48
49 STRV_FOREACH(name, names) {
50 _cleanup_free_ char *fragment_path = NULL;
51 _cleanup_strv_free_ char **dropin_paths = NULL;
52
53 r = unit_find_paths(bus, *name, &lp, false, &cached_id_map, &cached_name_map, &fragment_path, &dropin_paths);
54 if (r == -ERFKILL) {
55 printf("%s# Unit %s is masked%s.\n",
56 ansi_highlight_magenta(),
57 *name,
58 ansi_normal());
59 continue;
60 }
61 if (r == -EKEYREJECTED) {
62 printf("%s# Unit %s could not be loaded.%s\n",
63 ansi_highlight_magenta(),
64 *name,
65 ansi_normal());
66 continue;
67 }
68 if (r < 0)
69 return r;
70 if (r == 0) {
71 /* Skip units which have no on-disk counterpart, but propagate the error to the
72 * user */
73 rc = -ENOENT;
74 continue;
75 }
76
77 if (first)
78 first = false;
79 else
80 puts("");
81
82 if (need_daemon_reload(bus, *name) > 0) /* ignore errors (<0), this is informational output */
83 fprintf(stderr,
84 "%s# Warning: %s changed on disk, the version systemd has loaded is outdated.\n"
85 "%s# This output shows the current version of the unit's original fragment and drop-in files.\n"
86 "%s# If fragments or drop-ins were added or removed, they are not properly reflected in this output.\n"
87 "%s# Run 'systemctl%s daemon-reload' to reload units.%s\n",
88 ansi_highlight_red(),
89 *name,
90 ansi_highlight_red(),
91 ansi_highlight_red(),
92 ansi_highlight_red(),
93 arg_runtime_scope == RUNTIME_SCOPE_SYSTEM ? "" : " --user",
94 ansi_normal());
95
96 r = cat_files(fragment_path, dropin_paths, /* flags= */ CAT_FORMAT_HAS_SECTIONS);
97 if (r < 0)
98 return r;
99 }
100
101 return rc;
102 }
103
104 static int get_file_to_edit(
105 const LookupPaths *lp,
106 const char *name,
107 char **ret_path) {
108
109 _cleanup_free_ char *path = NULL;
110
111 assert(lp);
112 assert(name);
113 assert(ret_path);
114
115 path = path_join(lp->persistent_config, name);
116 if (!path)
117 return log_oom();
118
119 if (arg_runtime) {
120 _cleanup_free_ char *run = NULL;
121
122 run = path_join(lp->runtime_config, name);
123 if (!run)
124 return log_oom();
125
126 if (access(path, F_OK) >= 0)
127 return log_error_errno(SYNTHETIC_ERRNO(EEXIST),
128 "Refusing to create \"%s\" because it would be overridden by \"%s\" anyway.",
129 run, path);
130
131 *ret_path = TAKE_PTR(run);
132 } else
133 *ret_path = TAKE_PTR(path);
134
135 return 0;
136 }
137
138 static int unit_file_create_new(
139 EditFileContext *context,
140 const LookupPaths *lp,
141 const char *unit_name,
142 const char *suffix,
143 char * const *original_unit_paths) {
144
145 _cleanup_free_ char *unit = NULL, *new_path = NULL;
146 int r;
147
148 assert(context);
149 assert(lp);
150 assert(unit_name);
151
152 unit = strjoin(unit_name, suffix);
153 if (!unit)
154 return log_oom();
155
156 r = get_file_to_edit(lp, unit, &new_path);
157 if (r < 0)
158 return r;
159
160 return edit_files_add(context, new_path, NULL, original_unit_paths);
161 }
162
163 static int unit_file_create_copy(
164 EditFileContext *context,
165 const LookupPaths *lp,
166 const char *unit_name,
167 const char *fragment_path) {
168
169 _cleanup_free_ char *new_path = NULL;
170 int r;
171
172 assert(context);
173 assert(lp);
174 assert(fragment_path);
175 assert(unit_name);
176
177 r = get_file_to_edit(lp, unit_name, &new_path);
178 if (r < 0)
179 return r;
180
181 if (!path_equal(fragment_path, new_path) && access(new_path, F_OK) >= 0) {
182 char response;
183
184 r = ask_char(&response, "yn", "\"%s\" already exists. Overwrite with \"%s\"? [(y)es, (n)o] ", new_path, fragment_path);
185 if (r < 0)
186 return r;
187
188 if (response != 'y')
189 return log_warning_errno(SYNTHETIC_ERRNO(EKEYREJECTED), "%s skipped.", unit_name);
190 }
191
192 return edit_files_add(context, new_path, fragment_path, NULL);
193 }
194
195 static int find_paths_to_edit(
196 sd_bus *bus,
197 EditFileContext *context,
198 char **names) {
199
200 _cleanup_hashmap_free_ Hashmap *cached_id_map = NULL, *cached_name_map = NULL;
201 _cleanup_(lookup_paths_free) LookupPaths lp = {};
202 _cleanup_free_ char *drop_in_alloc = NULL, *suffix = NULL;
203 const char *drop_in;
204 int r;
205
206 assert(bus);
207 assert(context);
208 assert(names);
209
210 if (isempty(arg_drop_in))
211 drop_in = "override.conf";
212 else if (!endswith(arg_drop_in, ".conf")) {
213 drop_in_alloc = strjoin(arg_drop_in, ".conf");
214 if (!drop_in_alloc)
215 return log_oom();
216
217 drop_in = drop_in_alloc;
218 } else
219 drop_in = arg_drop_in;
220
221 if (!filename_is_valid(drop_in))
222 return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Invalid drop-in file name '%s'.", drop_in);
223
224 suffix = strjoin(".d/", drop_in);
225 if (!suffix)
226 return log_oom();
227
228 r = lookup_paths_init(&lp, arg_runtime_scope, 0, arg_root);
229 if (r < 0)
230 return r;
231
232 STRV_FOREACH(name, names) {
233 _cleanup_free_ char *path = NULL;
234 _cleanup_strv_free_ char **unit_paths = NULL;
235
236 r = unit_find_paths(bus, *name, &lp, /* force_client_side= */ false, &cached_id_map, &cached_name_map, &path, &unit_paths);
237 if (r == -EKEYREJECTED) {
238 /* If loading of the unit failed server side complete, then the server won't tell us
239 * the unit file path. In that case, find the file client side. */
240
241 log_debug_errno(r, "Unit '%s' was not loaded correctly, retrying client-side.", *name);
242 r = unit_find_paths(bus, *name, &lp, /* force_client_side= */ true, &cached_id_map, &cached_name_map, &path, &unit_paths);
243 }
244 if (r == -ERFKILL)
245 return log_error_errno(r, "Unit '%s' masked, cannot edit.", *name);
246 if (r < 0)
247 return r; /* Already logged by unit_find_paths() */
248
249 if (!path) {
250 if (!arg_force)
251 return log_info_errno(SYNTHETIC_ERRNO(ENOENT),
252 "Run 'systemctl edit%s --force --full %s' to create a new unit.",
253 arg_runtime_scope == RUNTIME_SCOPE_GLOBAL ? " --global" :
254 arg_runtime_scope == RUNTIME_SCOPE_USER ? " --user" : "",
255 *name);
256
257 /* Create a new unit from scratch */
258 r = unit_file_create_new(
259 context,
260 &lp,
261 *name,
262 arg_full ? NULL : suffix,
263 NULL);
264 } else {
265 _cleanup_free_ char *unit_name = NULL;
266
267 r = path_extract_filename(path, &unit_name);
268 if (r < 0)
269 return log_error_errno(r, "Failed to extract unit name from path '%s': %m", path);
270
271 /* We follow unit aliases, but we need to propagate the instance */
272 if (unit_name_is_valid(*name, UNIT_NAME_INSTANCE) &&
273 unit_name_is_valid(unit_name, UNIT_NAME_TEMPLATE)) {
274 _cleanup_free_ char *instance = NULL, *tmp_name = NULL;
275
276 r = unit_name_to_instance(*name, &instance);
277 if (r < 0)
278 return r;
279
280 r = unit_name_replace_instance(unit_name, instance, &tmp_name);
281 if (r < 0)
282 return r;
283
284 free_and_replace(unit_name, tmp_name);
285 }
286
287 if (arg_full)
288 r = unit_file_create_copy(
289 context,
290 &lp,
291 unit_name,
292 path);
293 else {
294 r = strv_prepend(&unit_paths, path);
295 if (r < 0)
296 return log_oom();
297
298 r = unit_file_create_new(
299 context,
300 &lp,
301 unit_name,
302 suffix,
303 unit_paths);
304 }
305 }
306 if (r < 0)
307 return r;
308 }
309
310 return 0;
311 }
312
313 int verb_edit(int argc, char *argv[], void *userdata) {
314 _cleanup_(edit_file_context_done) EditFileContext context = {
315 .marker_start = DROPIN_MARKER_START,
316 .marker_end = DROPIN_MARKER_END,
317 .remove_parent = !arg_full,
318 .overwrite_with_origin = true,
319 .stdin = arg_stdin,
320 };
321 _cleanup_strv_free_ char **names = NULL;
322 sd_bus *bus;
323 int r;
324
325 if (!on_tty() && !arg_stdin)
326 return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Cannot edit units if not on a tty.");
327
328 if (arg_transport != BUS_TRANSPORT_LOCAL)
329 return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Cannot edit units remotely.");
330
331 r = mac_init();
332 if (r < 0)
333 return r;
334
335 r = acquire_bus(BUS_MANAGER, &bus);
336 if (r < 0)
337 return r;
338
339 r = expand_unit_names(bus, strv_skip(argv, 1), NULL, &names, NULL);
340 if (r < 0)
341 return log_error_errno(r, "Failed to expand names: %m");
342 if (strv_isempty(names))
343 return log_error_errno(SYNTHETIC_ERRNO(ENOENT), "No units matched the specified patterns.");
344
345 if (arg_stdin && arg_full && strv_length(names) != 1)
346 return log_error_errno(SYNTHETIC_ERRNO(EINVAL),
347 "With 'edit --stdin --full', exactly one unit for editing must be specified.");
348
349 STRV_FOREACH(tmp, names) {
350 r = unit_is_masked(bus, *tmp);
351 if (r < 0)
352 return r;
353 if (r > 0)
354 return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Cannot edit %s: unit is masked.", *tmp);
355 }
356
357 r = find_paths_to_edit(bus, &context, names);
358 if (r < 0)
359 return r;
360
361 r = do_edit_files_and_install(&context);
362 if (r < 0)
363 return r;
364
365 if (!arg_no_reload && !install_client_side()) {
366 r = daemon_reload(ACTION_RELOAD, /* graceful= */ false);
367 if (r < 0)
368 return r;
369 }
370
371 return 0;
372 }