diff --git a/systemd/config b/systemd/config index a316c83e5..c09206786 100644 --- a/systemd/config +++ b/systemd/config @@ -5,6 +5,7 @@ visible_tabs=service,timer,socket,path,target,storage,resources,device,user show_runtime_units=1 default_create_scope=system manual_vendor_units=1 +delete_vendor_units=0 default_linger=1 show_unit_suffixes=0 show_dropin_inventory=1 diff --git a/systemd/config.info b/systemd/config.info index dd1407c7e..3a29cad55 100644 --- a/systemd/config.info +++ b/systemd/config.info @@ -5,6 +5,7 @@ visible_tabs=Tabs to show on the index page,15,visible_tabs show_runtime_units=Show generated and transient units,1,1-Yes,0-No default_create_scope=Default scope for new units,1,system-System units,user-User units manual_vendor_units=Include vendor unit files in the manual editor,1,1-Yes,0-No +delete_vendor_units=Allow deleting packaged unit files,1,1-Yes,0-No default_linger=Enable linger by default for new user units,1,1-Yes,0-No show_unit_suffixes=Show full unit names with type suffixes,1,1-Yes,0-No show_dropin_inventory=Show drop-in override inventory,1,1-Yes,0-No diff --git a/systemd/edit_unit.cgi b/systemd/edit_unit.cgi index 82c3db864..e3806cb61 100755 --- a/systemd/edit_unit.cgi +++ b/systemd/edit_unit.cgi @@ -1087,7 +1087,8 @@ else { [ 'delete_override', $text{'edit_deleteoverridenow'} || "Delete Override" ]); } - elsif ($unit_file_editable && $in{'name'} ne 'webmin.service' && + elsif (($edit_user_scope || system_unit_file_deletable($u)) && + $unit_file_editable && $in{'name'} ne 'webmin.service' && systemd_can_delete($edit_user_scope, $unituser)) { push(@delete_buttons, [ 'delete', $text{'delete'} ]); } diff --git a/systemd/help/config_delete_vendor_units.html b/systemd/help/config_delete_vendor_units.html new file mode 100644 index 000000000..2c0ea6ecb --- /dev/null +++ b/systemd/help/config_delete_vendor_units.html @@ -0,0 +1,9 @@ +
Allow deleting packaged unit files
+

Controls whether the module may delete unit files from package-managed +vendor directories such as /usr/lib/systemd/system or +/lib/systemd/system. This is disabled by default because package +updates may restore or expect those files.

+ +

When set to no, only local administrator-created unit files under +/etc/systemd/system can be deleted. Packaged units can still be +disabled, masked, or customized with drop-in overrides.

diff --git a/systemd/lang/en b/systemd/lang/en index 36c768f5f..9dc88ad83 100644 --- a/systemd/lang/en +++ b/systemd/lang/en @@ -217,6 +217,7 @@ systemd_title2_user=Edit Systemd User Unit systemd_title2_view=View Systemd Unit systemd_title2_view_user=View Systemd User Unit systemd_egone=Unit no longer exists! +systemd_elocaldelete=Deleting packaged unit files is disabled in the module configuration. Disable, mask or create a drop-in override, or enable packaged unit deletion first. systemd_header=Systemd unit details systemd_name=Unit name systemd_type=Unit type diff --git a/systemd/systemd-lib.pl b/systemd/systemd-lib.pl index d6f466967..737edb1db 100644 --- a/systemd/systemd-lib.pl +++ b/systemd/systemd-lib.pl @@ -32,6 +32,9 @@ $config{"default_create_scope"} = "system" $config{"default_create_scope"} !~ /^(system|user)$/); $config{"manual_vendor_units"} = 1 if (!defined($config{"manual_vendor_units"})); +$config{"delete_vendor_units"} = 0 + if (!defined($config{"delete_vendor_units"}) || + $config{"delete_vendor_units"} !~ /^[01]$/); $config{"default_linger"} = 1 if (!defined($config{"default_linger"})); $config{"show_unit_suffixes"} = 0 @@ -2853,7 +2856,7 @@ Returns true if a system unit root is the local administrator directory. sub local_unit_file_root { my ($root) = @_; -return $root eq "/etc/systemd/system"; +return $root eq get_local_unit_root(); } =head2 get_system_unit_file_root_candidates() @@ -2863,7 +2866,7 @@ Returns possible systemd unit directories before existence and symlink checks. =cut sub get_system_unit_file_root_candidates { -return ("/etc/systemd/system", +return (get_local_unit_root(), "/usr/lib/systemd/system", "/lib/systemd/system"); } @@ -3417,18 +3420,23 @@ return reload_user_manager($user); =head2 delete_system_unit(name) -Delete all traces of some systemd unit. +Delete a permitted systemd unit file. =cut sub delete_system_unit { my ($name) = @_; return (0, $text{'systemd_ename'}) if (!valid_unit_name($name)); -my $file = get_unit_root($name)."/".$name; -return (0, $text{'systemd_egone'}) if (!-e $file && !-l $file); -unlink_logged($file); -reload_manager(); -return (1, ""); +foreach my $root (get_system_unit_file_root_candidates()) { + my $file = $root."/".$name; + next if (!-e $file && !-l $file); + return (0, $text{'systemd_elocaldelete'}) + if (!system_unit_root_delete_allowed($root)); + unlink_logged($file); + reload_manager(); + return (1, ""); + } +return (0, $text{'systemd_egone'}); } =head2 get_unit_types() @@ -3558,6 +3566,48 @@ return 0 if ($unit->{'file'} =~ m{/systemd/(transient|generator)/}); return 1; } +=head2 system_unit_file_deletable(&unit) + +Returns 1 if a system unit record points to a unit file that can be removed +under the current module configuration. + +=cut +sub system_unit_file_deletable +{ +my ($unit) = @_; +return 0 if (!unit_file_editable($unit)); +return system_unit_file_delete_allowed($unit->{'file'}, $unit->{'name'}); +} + +=head2 system_unit_file_delete_allowed(file, name) + +Returns 1 if a system unit path is in a root where deleting unit files is +currently permitted. + +=cut +sub system_unit_file_delete_allowed +{ +my ($file, $name) = @_; +return 0 if (!$file || !$name || !valid_unit_name($name)); +foreach my $root (get_system_unit_file_root_candidates()) { + next if (!system_unit_root_delete_allowed($root)); + return 1 if ($file eq $root."/".$name); + } +return 0; +} + +=head2 system_unit_root_delete_allowed(root) + +Returns 1 if system unit files under this root may be deleted. + +=cut +sub system_unit_root_delete_allowed +{ +my ($root) = @_; +return (local_unit_file_root($root) || + $config{'delete_vendor_units'} eq '1') ? 1 : 0; +} + =head2 unit_visible_on_index(&unit) Returns 1 if a unit should be included on index tabs, honoring the option to @@ -3701,7 +3751,7 @@ sub get_unit_root { my ($name, $packaged) = @_; # Common system and vendor unit directories. -my $systemd_local_conf = "/etc/systemd/system"; +my $systemd_local_conf = get_local_unit_root(); my $systemd_unit_dir1 = "/usr/lib/systemd/system"; my $systemd_unit_dir2 = "/lib/systemd/system"; if ($name) { @@ -3713,7 +3763,7 @@ if ($name) { return $p if (-r "$p/$name"); } } -# Always use /etc/systemd/system for locally created units. +# Always use the local administrator directory for locally created units. return $systemd_local_conf if (!$packaged && -d $systemd_local_conf); # Debian prefers /lib/systemd/system for packaged units. @@ -3729,6 +3779,16 @@ if (-d $systemd_unit_dir1) { return $systemd_unit_dir2; } +=head2 get_local_unit_root() + +Returns the local administrator directory for system unit files. + +=cut +sub get_local_unit_root +{ +return "/etc/systemd/system"; +} + =head2 get_unit_pid([name]) diff --git a/systemd/t/run-tests.t b/systemd/t/run-tests.t index 27b5a1615..d45c5a7d4 100644 --- a/systemd/t/run-tests.t +++ b/systemd/t/run-tests.t @@ -80,6 +80,7 @@ our (%access, %config, %in, %text, %gconfig, $remote_user); systemd_ereadonly => 'runtime unit file', systemd_ename => 'bad unit name', systemd_egone => 'unit gone', + systemd_elocaldelete => 'local unit delete only', systemd_eclash => 'unit clash', systemd_emountwhat => 'missing mount source', systemd_emountname => 'bad mount name', @@ -196,6 +197,27 @@ ok(!unit_file_editable({ unitstate => 'generated', }), 'generated unit files are read-only'); +ok(system_unit_file_deletable({ + name => 'local.service', + file => '/etc/systemd/system/local.service', + unitstate => 'enabled', + }), + 'local system unit files can be deleted'); +ok(!system_unit_file_deletable({ + name => 'vendor.service', + file => '/usr/lib/systemd/system/vendor.service', + unitstate => 'enabled', + }), + 'packaged system unit files cannot be deleted'); +{ + local $config{'delete_vendor_units'} = 1; + ok(system_unit_file_deletable({ + name => 'vendor.service', + file => '/usr/lib/systemd/system/vendor.service', + unitstate => 'enabled', + }), + 'packaged system unit files can be deleted when configured'); +} { local $config{'show_runtime_units'} = 0; ok(unit_visible_on_index({ @@ -908,9 +930,18 @@ like(get_unit_root(), qr{^/(etc|usr/lib|lib)/systemd/system$}, { my $root = "$work/system-root"; - make_path($root); + my $vendor_root = "$work/system-vendor-root"; + make_path($root, $vendor_root); my $reloaded = 0; - local *main::get_unit_root = sub { return $root }; + local *main::get_local_unit_root = sub { return $root }; + local *main::get_unit_root = sub { + my ($name) = @_; + return $vendor_root if (defined($name) && $name eq 'vendor.service'); + return $root; + }; + local *main::get_system_unit_file_root_candidates = sub { + return ($root, $vendor_root); + }; local *main::reload_manager = sub { $reloaded++ }; local *main::has_command = sub { return }; my ($ok) = create_system_unit( @@ -949,6 +980,20 @@ like(get_unit_root(), qr{^/(etc|usr/lib|lib)/systemd/system$}, ok(!$ok, 'delete_system_unit rejects already-missing unit'); is($out, $text{'systemd_egone'}, 'delete_system_unit reports stale missing unit'); + write_test_file("$vendor_root/vendor.service", "packaged"); + ($ok, $out) = delete_system_unit('vendor.service'); + ok(!$ok, 'delete_system_unit rejects packaged system unit files'); + is($out, $text{'systemd_elocaldelete'}, + 'delete_system_unit reports local-only delete policy'); + ok(-e "$vendor_root/vendor.service", + 'delete_system_unit leaves packaged system unit file alone'); + { + local $config{'delete_vendor_units'} = 1; + ($ok, $out) = delete_system_unit('vendor.service'); + ok($ok, 'delete_system_unit can delete packaged unit files when configured'); + ok(!-e "$vendor_root/vendor.service", + 'delete_system_unit removes configured packaged unit file'); + } } { @@ -1999,6 +2044,8 @@ like($config_source, qr/^default_create_scope=/m, 'module config exposes default create scope'); like($config_source, qr/^manual_vendor_units=/m, 'module config exposes vendor-file manual editor visibility'); +like($config_source, qr/^delete_vendor_units=/m, + 'module config exposes packaged unit delete policy'); like($config_source, qr/^default_linger=/m, 'module config exposes default linger choice'); like($config_source, qr/^show_dropin_inventory=/m, @@ -2236,6 +2283,8 @@ like($edit_source, qr/readonly='readonly'/, 'edit page shows runtime-managed unit files as read-only'); like($edit_source, qr/unit_file_editable/, 'edit page hides save and delete for runtime-managed unit files'); +like($edit_source, qr/system_unit_file_deletable/, + 'edit page hides delete for packaged system unit files'); like($edit_source, qr/edit_depsnow/, 'edit page includes dependency inspect action'); like($edit_source, qr/edit_propsnow/,