1 /* SPDX-License-Identifier: LGPL-2.1-or-later */
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"
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
;
24 /* Include all units by default — i.e. continue as if the --all option was used */
25 if (strv_isempty(arg_states
))
28 if (arg_transport
!= BUS_TRANSPORT_LOCAL
)
29 return log_error_errno(SYNTHETIC_ERRNO(EINVAL
), "Cannot remotely cat units.");
31 r
= lookup_paths_init_or_warn(&lp
, arg_runtime_scope
, 0, arg_root
);
35 r
= acquire_bus(BUS_MANAGER
, &bus
);
39 r
= expand_unit_names(bus
, strv_skip(argv
, 1), NULL
, &names
, NULL
);
41 return log_error_errno(r
, "Failed to expand names: %m");
43 r
= maybe_extend_with_unit_dependencies(bus
, &names
);
47 pager_open(arg_pager_flags
);
49 STRV_FOREACH(name
, names
) {
50 _cleanup_free_
char *fragment_path
= NULL
;
51 _cleanup_strv_free_
char **dropin_paths
= NULL
;
53 r
= unit_find_paths(bus
, *name
, &lp
, false, &cached_id_map
, &cached_name_map
, &fragment_path
, &dropin_paths
);
55 printf("%s# Unit %s is masked%s.\n",
56 ansi_highlight_magenta(),
61 if (r
== -EKEYREJECTED
) {
62 printf("%s# Unit %s could not be loaded.%s\n",
63 ansi_highlight_magenta(),
71 /* Skip units which have no on-disk counterpart, but propagate the error to the
82 if (need_daemon_reload(bus
, *name
) > 0) /* ignore errors (<0), this is informational output */
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",
93 arg_runtime_scope
== RUNTIME_SCOPE_SYSTEM
? "" : " --user",
96 r
= cat_files(fragment_path
, dropin_paths
, /* flags= */ CAT_FORMAT_HAS_SECTIONS
);
104 static int get_file_to_edit(
105 const LookupPaths
*lp
,
109 _cleanup_free_
char *path
= NULL
;
115 path
= path_join(lp
->persistent_config
, name
);
120 _cleanup_free_
char *run
= NULL
;
122 run
= path_join(lp
->runtime_config
, name
);
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.",
131 *ret_path
= TAKE_PTR(run
);
133 *ret_path
= TAKE_PTR(path
);
138 static int unit_file_create_new(
139 EditFileContext
*context
,
140 const LookupPaths
*lp
,
141 const char *unit_name
,
143 char * const *original_unit_paths
) {
145 _cleanup_free_
char *unit
= NULL
, *new_path
= NULL
;
152 unit
= strjoin(unit_name
, suffix
);
156 r
= get_file_to_edit(lp
, unit
, &new_path
);
160 return edit_files_add(context
, new_path
, NULL
, original_unit_paths
);
163 static int unit_file_create_copy(
164 EditFileContext
*context
,
165 const LookupPaths
*lp
,
166 const char *unit_name
,
167 const char *fragment_path
) {
169 _cleanup_free_
char *new_path
= NULL
;
174 assert(fragment_path
);
177 r
= get_file_to_edit(lp
, unit_name
, &new_path
);
181 if (!path_equal(fragment_path
, new_path
) && access(new_path
, F_OK
) >= 0) {
184 r
= ask_char(&response
, "yn", "\"%s\" already exists. Overwrite with \"%s\"? [(y)es, (n)o] ", new_path
, fragment_path
);
189 return log_warning_errno(SYNTHETIC_ERRNO(EKEYREJECTED
), "%s skipped.", unit_name
);
192 return edit_files_add(context
, new_path
, fragment_path
, NULL
);
195 static int find_paths_to_edit(
197 EditFileContext
*context
,
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
;
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");
217 drop_in
= drop_in_alloc
;
219 drop_in
= arg_drop_in
;
221 if (!filename_is_valid(drop_in
))
222 return log_error_errno(SYNTHETIC_ERRNO(EINVAL
), "Invalid drop-in file name '%s'.", drop_in
);
224 suffix
= strjoin(".d/", drop_in
);
228 r
= lookup_paths_init(&lp
, arg_runtime_scope
, 0, arg_root
);
232 STRV_FOREACH(name
, names
) {
233 _cleanup_free_
char *path
= NULL
;
234 _cleanup_strv_free_
char **unit_paths
= NULL
;
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. */
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
);
245 return log_error_errno(r
, "Unit '%s' masked, cannot edit.", *name
);
247 return r
; /* Already logged by unit_find_paths() */
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" : "",
257 /* Create a new unit from scratch */
258 r
= unit_file_create_new(
262 arg_full
? NULL
: suffix
,
265 _cleanup_free_
char *unit_name
= NULL
;
267 r
= path_extract_filename(path
, &unit_name
);
269 return log_error_errno(r
, "Failed to extract unit name from path '%s': %m", path
);
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
;
276 r
= unit_name_to_instance(*name
, &instance
);
280 r
= unit_name_replace_instance(unit_name
, instance
, &tmp_name
);
284 free_and_replace(unit_name
, tmp_name
);
288 r
= unit_file_create_copy(
294 r
= strv_prepend(&unit_paths
, path
);
298 r
= unit_file_create_new(
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,
321 _cleanup_strv_free_
char **names
= NULL
;
325 if (!on_tty() && !arg_stdin
)
326 return log_error_errno(SYNTHETIC_ERRNO(EINVAL
), "Cannot edit units if not on a tty.");
328 if (arg_transport
!= BUS_TRANSPORT_LOCAL
)
329 return log_error_errno(SYNTHETIC_ERRNO(EINVAL
), "Cannot edit units remotely.");
335 r
= acquire_bus(BUS_MANAGER
, &bus
);
339 r
= expand_unit_names(bus
, strv_skip(argv
, 1), NULL
, &names
, NULL
);
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.");
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.");
349 STRV_FOREACH(tmp
, names
) {
350 r
= unit_is_masked(bus
, *tmp
);
354 return log_error_errno(SYNTHETIC_ERRNO(EINVAL
), "Cannot edit %s: unit is masked.", *tmp
);
357 r
= find_paths_to_edit(bus
, &context
, names
);
361 r
= do_edit_files_and_install(&context
);
365 if (!arg_no_reload
&& !install_client_side()) {
366 r
= daemon_reload(ACTION_RELOAD
, /* graceful= */ false);