You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 

451 lines
14 KiB

  1. #! /usr/bin/perl
  2. #
  3. # update-rc.d Update the links in /etc/rc[0-9S].d/
  4. #
  5. use strict;
  6. use warnings;
  7. # NB: All Perl modules used here must be in perl-base. Specifically, depending
  8. # on modules in perl-modules is not okay! See bug #716923
  9. my $initd = "/etc/init.d";
  10. my $etcd = "/etc/rc";
  11. my $notreally = 0;
  12. # Print usage message and die.
  13. sub usage {
  14. print STDERR "update-rc.d: error: @_\n" if ($#_ >= 0);
  15. print STDERR <<EOF;
  16. usage: update-rc.d [-n] [-f] <basename> remove
  17. update-rc.d [-n] [-f] <basename> defaults
  18. update-rc.d [-n] <basename> disable|enable [S|2|3|4|5]
  19. -n: not really
  20. -f: force
  21. The disable|enable API is not stable and might change in the future.
  22. EOF
  23. exit (1);
  24. }
  25. exit insserv_updatercd(@ARGV);
  26. sub save_last_action {
  27. # No-op (archive removed)
  28. }
  29. sub remove_last_action {
  30. # No-op (archive removed)
  31. }
  32. sub info {
  33. print STDOUT "update-rc.d: @_\n";
  34. }
  35. sub warning {
  36. print STDERR "update-rc.d: warning: @_\n";
  37. }
  38. sub error {
  39. print STDERR "update-rc.d: error: @_\n";
  40. exit (1);
  41. }
  42. sub error_code {
  43. my $rc = shift;
  44. print STDERR "update-rc.d: error: @_\n";
  45. exit ($rc);
  46. }
  47. sub make_path {
  48. my ($path) = @_;
  49. my @dirs = ();
  50. my @path = split /\//, $path;
  51. map { push @dirs, $_; mkdir join('/', @dirs), 0755; } @path;
  52. }
  53. # Given a script name, return any runlevels except 0 or 6 in which the
  54. # script is enabled. If that gives nothing and the script is not
  55. # explicitly disabled, return 6 if the script is disabled in runlevel
  56. # 0 or 6.
  57. sub script_runlevels {
  58. my ($scriptname) = @_;
  59. my @links=<"/etc/rc[S12345].d/S[0-9][0-9]$scriptname">;
  60. if (@links) {
  61. return map(substr($_, 7, 1), @links);
  62. } elsif (! <"/etc/rc[S12345].d/K[0-9][0-9]$scriptname">) {
  63. @links=<"/etc/rc[06].d/K[0-9][0-9]$scriptname">;
  64. return ("6") if (@links);
  65. } else {
  66. return ;
  67. }
  68. }
  69. # Map the sysvinit runlevel to that of openrc.
  70. sub openrc_rlconv {
  71. my %rl_table = (
  72. "S" => "sysinit",
  73. "1" => "recovery",
  74. "2" => "default",
  75. "3" => "default",
  76. "4" => "default",
  77. "5" => "default",
  78. "6" => "off" );
  79. my %seen; # return unique runlevels
  80. return grep !$seen{$_}++, map($rl_table{$_}, @_);
  81. }
  82. sub systemd_reload {
  83. if (-d "/run/systemd/system") {
  84. system("systemctl", "daemon-reload");
  85. }
  86. }
  87. # Creates the necessary links to enable/disable a SysV init script (fallback if
  88. # no insserv/rc-update exists)
  89. sub make_sysv_links {
  90. my ($scriptname, $action) = @_;
  91. # for "remove" we cannot rely on the init script still being present, as
  92. # this gets called in postrm for purging. Just remove all symlinks.
  93. if ("remove" eq $action) { unlink($_) for
  94. glob("/etc/rc?.d/[SK][0-9][0-9]$scriptname"); return; }
  95. # if the service already has any links, do not touch them
  96. # numbers we don't care about, but enabled/disabled state we do
  97. return if glob("/etc/rc?.d/[SK][0-9][0-9]$scriptname");
  98. # for "defaults", parse Default-{Start,Stop} and create these links
  99. my ($lsb_start_ref, $lsb_stop_ref) = parse_def_start_stop("/etc/init.d/$scriptname");
  100. foreach my $lvl (@$lsb_start_ref) {
  101. make_path("/etc/rc$lvl.d");
  102. my $l = "/etc/rc$lvl.d/S01$scriptname";
  103. symlink("../init.d/$scriptname", $l);
  104. }
  105. foreach my $lvl (@$lsb_stop_ref) {
  106. make_path("/etc/rc$lvl.d");
  107. my $l = "/etc/rc$lvl.d/K01$scriptname";
  108. symlink("../init.d/$scriptname", $l);
  109. }
  110. }
  111. # Creates the necessary links to enable/disable the service (equivalent of an
  112. # initscript) in systemd.
  113. sub make_systemd_links {
  114. my ($scriptname, $action) = @_;
  115. # In addition to the insserv call we also enable/disable the service
  116. # for systemd by creating the appropriate symlink in case there is a
  117. # native systemd service. We need to do this on our own instead of
  118. # using systemctl because systemd might not even be installed yet.
  119. my $service_path;
  120. if (-f "/etc/systemd/system/$scriptname.service") {
  121. $service_path = "/etc/systemd/system/$scriptname.service";
  122. } elsif (-f "/lib/systemd/system/$scriptname.service") {
  123. $service_path = "/lib/systemd/system/$scriptname.service";
  124. }
  125. if (defined($service_path)) {
  126. my $changed_sth;
  127. open my $fh, '<', $service_path or error("unable to read $service_path");
  128. while (<$fh>) {
  129. chomp;
  130. if (/^\s*WantedBy=(.+)$/i) {
  131. my $wants_dir = "/etc/systemd/system/$1.wants";
  132. my $service_link = "$wants_dir/$scriptname.service";
  133. if ("enable" eq $action) {
  134. make_path($wants_dir);
  135. symlink($service_path, $service_link);
  136. } else {
  137. unlink($service_link) if -e $service_link;
  138. }
  139. }
  140. }
  141. close($fh);
  142. }
  143. }
  144. ## Dependency based
  145. sub insserv_updatercd {
  146. my @args = @_;
  147. my @opts;
  148. my $scriptname;
  149. my $action;
  150. my $notreally = 0;
  151. my @orig_argv = @args;
  152. while($#args >= 0 && ($_ = $args[0]) =~ /^-/) {
  153. shift @args;
  154. if (/^-n$/) { push(@opts, $_); $notreally++; next }
  155. if (/^-f$/) { push(@opts, $_); next }
  156. if (/^-h|--help$/) { usage(); }
  157. usage("unknown option");
  158. }
  159. usage("not enough arguments") if ($#args < 1);
  160. # Add force flag if initscripts is not installed
  161. # This enables inistcripts-less systems to not fail when a facility is missing
  162. unshift(@opts, '-f') unless is_initscripts_installed();
  163. $scriptname = shift @args;
  164. $action = shift @args;
  165. my $insserv = "/usr/lib/insserv/insserv";
  166. # Fallback for older insserv package versions [2014-04-16]
  167. $insserv = "/sbin/insserv" if ( -x "/sbin/insserv");
  168. # If insserv is not configured it is not fully installed
  169. my $insserv_installed = -x $insserv && -e "/etc/insserv.conf";
  170. if ("remove" eq $action) {
  171. system("rc-update", "-qqa", "delete", $scriptname) if ( -x "/sbin/openrc" );
  172. if ( !$insserv_installed ) {
  173. # We are either under systemd or in a chroot where the link priorities don't matter
  174. make_sysv_links($scriptname, "remove");
  175. systemd_reload;
  176. exit 0;
  177. }
  178. if ( -f "/etc/init.d/$scriptname" ) {
  179. my $rc = system($insserv, @opts, "-r", $scriptname) >> 8;
  180. if (0 == $rc && !$notreally) {
  181. remove_last_action($scriptname);
  182. }
  183. error_code($rc, "insserv rejected the script header") if $rc;
  184. systemd_reload;
  185. exit $rc;
  186. } else {
  187. # insserv removes all dangling symlinks, no need to tell it
  188. # what to look for.
  189. my $rc = system($insserv, @opts) >> 8;
  190. if (0 == $rc && !$notreally) {
  191. remove_last_action($scriptname);
  192. }
  193. error_code($rc, "insserv rejected the script header") if $rc;
  194. systemd_reload;
  195. exit $rc;
  196. }
  197. } elsif ("defaults" eq $action || "start" eq $action ||
  198. "stop" eq $action) {
  199. # All start/stop/defaults arguments are discarded so emit a
  200. # message if arguments have been given and are in conflict
  201. # with Default-Start/Default-Stop values of LSB comment.
  202. if ("start" eq $action || "stop" eq $action) {
  203. cmp_args_with_defaults($scriptname, $action, @args);
  204. }
  205. if ( !$insserv_installed ) {
  206. # We are either under systemd or in a chroot where the link priorities don't matter
  207. make_sysv_links($scriptname, "defaults");
  208. systemd_reload;
  209. exit 0;
  210. }
  211. if ( -f "/etc/init.d/$scriptname" ) {
  212. my $rc = system($insserv, @opts, $scriptname) >> 8;
  213. if (0 == $rc && !$notreally) {
  214. save_last_action($scriptname, @orig_argv);
  215. }
  216. error_code($rc, "insserv rejected the script header") if $rc;
  217. systemd_reload;
  218. # OpenRC does not distinguish halt and reboot. They are handled
  219. # by /etc/init.d/transit instead.
  220. if ( -x "/sbin/openrc" && "halt" ne $scriptname
  221. && "reboot" ne $scriptname ) {
  222. # no need to consider default disabled runlevels
  223. # because everything is disabled by openrc by default
  224. my @rls=script_runlevels($scriptname);
  225. system("rc-update", "add", $scriptname, openrc_rlconv(@rls))
  226. if ( @rls );
  227. }
  228. exit $rc;
  229. } else {
  230. error("initscript does not exist: /etc/init.d/$scriptname");
  231. }
  232. } elsif ("disable" eq $action || "enable" eq $action) {
  233. make_systemd_links($scriptname, $action);
  234. sysv_toggle($notreally, $action, $scriptname, @args);
  235. if ( !$insserv_installed ) {
  236. # We are either under systemd or in a chroot where the link priorities don't matter
  237. systemd_reload;
  238. exit 0;
  239. }
  240. # Call insserv to resequence modified links
  241. my $rc = system($insserv, @opts, $scriptname) >> 8;
  242. if (0 == $rc && !$notreally) {
  243. save_last_action($scriptname, @orig_argv);
  244. }
  245. error_code($rc, "insserv rejected the script header") if $rc;
  246. systemd_reload;
  247. exit $rc;
  248. } else {
  249. usage();
  250. }
  251. }
  252. sub parse_def_start_stop {
  253. my $script = shift;
  254. my (%lsb, @def_start_lvls, @def_stop_lvls);
  255. open my $fh, '<', $script or error("unable to read $script");
  256. while (<$fh>) {
  257. chomp;
  258. if (m/^### BEGIN INIT INFO\s*$/) {
  259. $lsb{'begin'}++;
  260. }
  261. elsif (m/^### END INIT INFO\s*$/) {
  262. $lsb{'end'}++;
  263. last;
  264. }
  265. elsif ($lsb{'begin'} and not $lsb{'end'}) {
  266. if (m/^# Default-Start:\s*(\S?.*)$/) {
  267. @def_start_lvls = split(' ', $1);
  268. }
  269. if (m/^# Default-Stop:\s*(\S?.*)$/) {
  270. @def_stop_lvls = split(' ', $1);
  271. }
  272. }
  273. }
  274. close($fh);
  275. return (\@def_start_lvls, \@def_stop_lvls);
  276. }
  277. sub lsb_header_for_script {
  278. my $name = shift;
  279. foreach my $file ("/etc/insserv/overrides/$name", "/etc/init.d/$name",
  280. "/usr/share/insserv/overrides/$name") {
  281. return $file if -s $file;
  282. }
  283. error("cannot find a LSB script for $name");
  284. }
  285. sub cmp_args_with_defaults {
  286. my ($name, $act) = (shift, shift);
  287. my ($lsb_start_ref, $lsb_stop_ref, $arg_str, $lsb_str);
  288. my (@arg_start_lvls, @arg_stop_lvls, @lsb_start_lvls, @lsb_stop_lvls);
  289. ($lsb_start_ref, $lsb_stop_ref) = parse_def_start_stop("/etc/init.d/$name");
  290. @lsb_start_lvls = @$lsb_start_ref;
  291. @lsb_stop_lvls = @$lsb_stop_ref;
  292. return if (!@lsb_start_lvls and !@lsb_stop_lvls);
  293. warning "start and stop actions are no longer supported; falling back to defaults";
  294. my $start = $act eq 'start' ? 1 : 0;
  295. my $stop = $act eq 'stop' ? 1 : 0;
  296. # The legacy part of this program passes arguments starting with
  297. # "start|stop NN x y z ." but the insserv part gives argument list
  298. # starting with sequence number (ie. strips off leading "start|stop")
  299. # Start processing arguments immediately after the first seq number.
  300. my $argi = $_[0] eq $act ? 2 : 1;
  301. while (defined $_[$argi]) {
  302. my $arg = $_[$argi];
  303. # Runlevels 0 and 6 are always stop runlevels
  304. if ($arg eq 0 or $arg eq 6) {
  305. $start = 0; $stop = 1;
  306. } elsif ($arg eq 'start') {
  307. $start = 1; $stop = 0; $argi++; next;
  308. } elsif ($arg eq 'stop') {
  309. $start = 0; $stop = 1; $argi++; next;
  310. } elsif ($arg eq '.') {
  311. next;
  312. }
  313. push(@arg_start_lvls, $arg) if $start;
  314. push(@arg_stop_lvls, $arg) if $stop;
  315. } continue {
  316. $argi++;
  317. }
  318. if ($#arg_start_lvls != $#lsb_start_lvls or
  319. join("\0", sort @arg_start_lvls) ne join("\0", sort @lsb_start_lvls)) {
  320. $arg_str = @arg_start_lvls ? "@arg_start_lvls" : "none";
  321. $lsb_str = @lsb_start_lvls ? "@lsb_start_lvls" : "none";
  322. warning "start runlevel arguments ($arg_str) do not match",
  323. "$name Default-Start values ($lsb_str)";
  324. }
  325. if ($#arg_stop_lvls != $#lsb_stop_lvls or
  326. join("\0", sort @arg_stop_lvls) ne join("\0", sort @lsb_stop_lvls)) {
  327. $arg_str = @arg_stop_lvls ? "@arg_stop_lvls" : "none";
  328. $lsb_str = @lsb_stop_lvls ? "@lsb_stop_lvls" : "none";
  329. warning "stop runlevel arguments ($arg_str) do not match",
  330. "$name Default-Stop values ($lsb_str)";
  331. }
  332. }
  333. sub sysv_toggle {
  334. my ($dryrun, $act, $name) = (shift, shift, shift);
  335. my (@toggle_lvls, $start_lvls, $stop_lvls, @symlinks);
  336. my $lsb_header = lsb_header_for_script($name);
  337. # Extra arguments to disable|enable action are runlevels. If none
  338. # given parse LSB info for Default-Start value.
  339. if ($#_ >= 0) {
  340. @toggle_lvls = @_;
  341. } else {
  342. ($start_lvls, $stop_lvls) = parse_def_start_stop($lsb_header);
  343. @toggle_lvls = @$start_lvls;
  344. if ($#toggle_lvls < 0) {
  345. error("$name Default-Start contains no runlevels, aborting.");
  346. }
  347. }
  348. if ( -x "/sbin/openrc" ) {
  349. my %openrc_act = ( "disable" => "del", "enable" => "add" );
  350. system("rc-update", $openrc_act{$act}, $name,
  351. openrc_rlconv(@toggle_lvls))
  352. }
  353. # Find symlinks in rc.d directories. Refuse to modify links in runlevels
  354. # not used for normal system start sequence.
  355. for my $lvl (@toggle_lvls) {
  356. if ($lvl !~ /^[S2345]$/) {
  357. warning("$act action will have no effect on runlevel $lvl");
  358. next;
  359. }
  360. push(@symlinks, $_) for glob("/etc/rc$lvl.d/[SK][0-9][0-9]$name");
  361. }
  362. if (!@symlinks) {
  363. error("no runlevel symlinks to modify, aborting!");
  364. }
  365. # Toggle S/K bit of script symlink.
  366. for my $cur_lnk (@symlinks) {
  367. my $sk;
  368. my @new_lnk = split(//, $cur_lnk);
  369. if ("disable" eq $act) {
  370. $sk = rindex($cur_lnk, '/S') + 1;
  371. next if $sk < 1;
  372. $new_lnk[$sk] = 'K';
  373. } else {
  374. $sk = rindex($cur_lnk, '/K') + 1;
  375. next if $sk < 1;
  376. $new_lnk[$sk] = 'S';
  377. }
  378. if ($dryrun) {
  379. printf("rename(%s, %s)\n", $cur_lnk, join('', @new_lnk));
  380. next;
  381. }
  382. rename($cur_lnk, join('', @new_lnk)) or error($!);
  383. }
  384. }
  385. # Try to determine if initscripts is installed
  386. sub is_initscripts_installed {
  387. # Check if mountkernfs is available. We cannot make inferences
  388. # using the running init system because we may be running in a
  389. # chroot
  390. return glob('/etc/rcS.d/S??mountkernfs.sh');
  391. }