1 /* SPDX-License-Identifier: LGPL-2.1-or-later */
5 #include "alloc-util.h"
9 #include "label-util.h"
11 #include "path-lookup.h"
12 #include "path-util.h"
13 #include "pretty-print.h"
14 #include "string-util.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"
23 int verb_cat(int argc
, char *argv
[], void *userdata
) {
24 _cleanup_hashmap_free_ Hashmap
*cached_id_map
= NULL
, *cached_name_map
= NULL
;
25 _cleanup_(lookup_paths_done
) LookupPaths lp
= {};
26 _cleanup_strv_free_
char **names
= NULL
;
31 /* Include all units by default — i.e. continue as if the --all option was used */
32 if (strv_isempty(arg_states
))
35 if (arg_transport
!= BUS_TRANSPORT_LOCAL
)
36 return log_error_errno(SYNTHETIC_ERRNO(EINVAL
), "Cannot remotely cat units.");
38 r
= lookup_paths_init_or_warn(&lp
, arg_runtime_scope
, 0, arg_root
);
42 r
= acquire_bus(BUS_MANAGER
, &bus
);
46 r
= expand_unit_names(bus
, strv_skip(argv
, 1), NULL
, &names
, NULL
);
48 return log_error_errno(r
, "Failed to expand names: %m");
50 r
= maybe_extend_with_unit_dependencies(bus
, &names
);
54 pager_open(arg_pager_flags
);
56 STRV_FOREACH(name
, names
) {
57 _cleanup_free_
char *fragment_path
= NULL
;
58 _cleanup_strv_free_
char **dropin_paths
= NULL
;
60 r
= unit_find_paths(bus
, *name
, &lp
, false, &cached_id_map
, &cached_name_map
, &fragment_path
, &dropin_paths
);
62 printf("%s# Unit %s is masked%s.\n",
63 ansi_highlight_magenta(),
68 if (r
== -EKEYREJECTED
) {
69 printf("%s# Unit %s could not be loaded.%s\n",
70 ansi_highlight_magenta(),
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()) */
90 if (need_daemon_reload(bus
, *name
) > 0) /* ignore errors (<0), this is informational output */
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",
100 ansi_highlight_red(),
101 arg_runtime_scope
== RUNTIME_SCOPE_SYSTEM
? "" : " --user",
104 r
= cat_files(fragment_path
, dropin_paths
, /* flags= */ CAT_FORMAT_HAS_SECTIONS
);
112 static int get_file_to_edit(
113 const LookupPaths
*lp
,
117 _cleanup_free_
char *path
= NULL
;
123 path
= path_join(lp
->persistent_config
, name
);
128 _cleanup_free_
char *run
= NULL
;
130 run
= path_join(lp
->runtime_config
, name
);
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.",
139 *ret_path
= TAKE_PTR(run
);
141 *ret_path
= TAKE_PTR(path
);
146 static int unit_file_create_new(
147 EditFileContext
*context
,
148 const LookupPaths
*lp
,
149 const char *unit_name
,
151 char * const *original_unit_paths
) {
153 _cleanup_free_
char *unit
= NULL
, *new_path
= NULL
;
160 unit
= strjoin(unit_name
, suffix
);
164 r
= get_file_to_edit(lp
, unit
, &new_path
);
168 return edit_files_add(context
, new_path
, NULL
, original_unit_paths
);
171 static int unit_file_create_copy(
172 EditFileContext
*context
,
173 const LookupPaths
*lp
,
174 const char *unit_name
,
175 const char *fragment_path
) {
177 _cleanup_free_
char *new_path
= NULL
;
182 assert(fragment_path
);
185 r
= get_file_to_edit(lp
, unit_name
, &new_path
);
189 if (!path_equal(fragment_path
, new_path
) && access(new_path
, F_OK
) >= 0) {
192 r
= ask_char(&response
, "yn", "\"%s\" already exists. Overwrite with \"%s\"? [(y)es, (n)o] ", new_path
, fragment_path
);
197 return log_warning_errno(SYNTHETIC_ERRNO(EKEYREJECTED
), "%s skipped.", unit_name
);
200 return edit_files_add(context
, new_path
, fragment_path
, NULL
);
203 static int find_paths_to_edit(
205 EditFileContext
*context
,
208 _cleanup_hashmap_free_ Hashmap
*cached_id_map
= NULL
, *cached_name_map
= NULL
;
209 _cleanup_(lookup_paths_done
) LookupPaths lp
= {};
210 _cleanup_free_
char *drop_in_alloc
= NULL
, *suffix
= NULL
;
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");
225 drop_in
= drop_in_alloc
;
227 drop_in
= arg_drop_in
;
229 if (!filename_is_valid(drop_in
))
230 return log_error_errno(SYNTHETIC_ERRNO(EINVAL
), "Invalid drop-in file name '%s'.", drop_in
);
232 suffix
= strjoin(".d/", drop_in
);
236 r
= lookup_paths_init(&lp
, arg_runtime_scope
, 0, arg_root
);
240 STRV_FOREACH(name
, names
) {
241 _cleanup_free_
char *path
= NULL
;
242 _cleanup_strv_free_
char **unit_paths
= NULL
;
244 r
= unit_find_paths(bus
, *name
, &lp
, /* force_client_side= */ false, &cached_id_map
, &cached_name_map
, &path
, &unit_paths
);
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. */
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
);
253 return log_error_errno(r
, "Unit '%s' masked, cannot edit.", *name
);
255 return r
; /* Already logged by unit_find_paths() */
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" : "",
265 /* Create a new unit from scratch */
266 r
= unit_file_create_new(
270 arg_full
? NULL
: suffix
,
273 _cleanup_free_
char *unit_name
= NULL
;
275 r
= path_extract_filename(path
, &unit_name
);
277 return log_error_errno(r
, "Failed to extract unit name from path '%s': %m", path
);
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
)) {
282 _cleanup_free_
char *instance
= NULL
, *tmp_name
= NULL
;
284 r
= unit_name_to_instance(*name
, &instance
);
288 r
= unit_name_replace_instance(unit_name
, instance
, &tmp_name
);
292 free_and_replace(unit_name
, tmp_name
);
296 r
= unit_file_create_copy(
302 r
= strv_prepend(&unit_paths
, path
);
306 r
= unit_file_create_new(
321 int verb_edit(int argc
, char *argv
[], void *userdata
) {
322 _cleanup_(edit_file_context_done
) EditFileContext context
= {
323 .marker_start
= DROPIN_MARKER_START
,
324 .marker_end
= DROPIN_MARKER_END
,
325 .remove_parent
= !arg_full
,
326 .overwrite_with_origin
= true,
327 .read_from_stdin
= arg_stdin
,
329 _cleanup_strv_free_
char **names
= NULL
;
333 if (!on_tty() && !arg_stdin
)
334 return log_error_errno(SYNTHETIC_ERRNO(EINVAL
), "Cannot edit units interactively if not on a tty.");
336 if (arg_transport
!= BUS_TRANSPORT_LOCAL
)
337 return log_error_errno(SYNTHETIC_ERRNO(EINVAL
), "Cannot edit units remotely.");
343 r
= acquire_bus(BUS_MANAGER
, &bus
);
347 r
= expand_unit_names(bus
, strv_skip(argv
, 1), NULL
, &names
, NULL
);
349 return log_error_errno(r
, "Failed to expand names: %m");
350 if (strv_isempty(names
))
351 return log_error_errno(SYNTHETIC_ERRNO(ENOENT
), "No units matched the specified patterns.");
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.");
357 STRV_FOREACH(tmp
, names
) {
358 r
= unit_is_masked(bus
, *tmp
);
359 if (r
< 0 && r
!= -ENOENT
)
360 return log_error_errno(r
, "Failed to check if unit %s is masked: %m", *tmp
);
362 return log_error_errno(SYNTHETIC_ERRNO(EINVAL
), "Cannot edit %s: unit is masked.", *tmp
);
365 r
= find_paths_to_edit(bus
, &context
, names
);
369 r
= do_edit_files_and_install(&context
);
373 if (!arg_no_reload
&& !install_client_side()) {
374 r
= daemon_reload(ACTION_RELOAD
, /* graceful= */ false);