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.
 
 
 
 

449 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 ("remove" eq $action) {
  169. system("rc-update", "-qqa", "delete", $scriptname) if ( -x "/sbin/openrc" );
  170. if ( ! -x $insserv) {
  171. # We are either under systemd or in a chroot where the link priorities don't matter
  172. make_sysv_links($scriptname, "remove");
  173. systemd_reload;
  174. exit 0;
  175. }
  176. if ( -f "/etc/init.d/$scriptname" ) {
  177. my $rc = system($insserv, @opts, "-r", $scriptname) >> 8;
  178. if (0 == $rc && !$notreally) {
  179. remove_last_action($scriptname);
  180. }
  181. error_code($rc, "insserv rejected the script header") if $rc;
  182. systemd_reload;
  183. exit $rc;
  184. } else {
  185. # insserv removes all dangling symlinks, no need to tell it
  186. # what to look for.
  187. my $rc = system($insserv, @opts) >> 8;
  188. if (0 == $rc && !$notreally) {
  189. remove_last_action($scriptname);
  190. }
  191. error_code($rc, "insserv rejected the script header") if $rc;
  192. systemd_reload;
  193. exit $rc;
  194. }
  195. } elsif ("defaults" eq $action || "start" eq $action ||
  196. "stop" eq $action) {
  197. # All start/stop/defaults arguments are discarded so emit a
  198. # message if arguments have been given and are in conflict
  199. # with Default-Start/Default-Stop values of LSB comment.
  200. if ("start" eq $action || "stop" eq $action) {
  201. cmp_args_with_defaults($scriptname, $action, @args);
  202. }
  203. if ( ! -x $insserv) {
  204. # We are either under systemd or in a chroot where the link priorities don't matter
  205. make_sysv_links($scriptname, "defaults");
  206. systemd_reload;
  207. exit 0;
  208. }
  209. if ( -f "/etc/init.d/$scriptname" ) {
  210. my $rc = system($insserv, @opts, $scriptname) >> 8;
  211. if (0 == $rc && !$notreally) {
  212. save_last_action($scriptname, @orig_argv);
  213. }
  214. error_code($rc, "insserv rejected the script header") if $rc;
  215. systemd_reload;
  216. # OpenRC does not distinguish halt and reboot. They are handled
  217. # by /etc/init.d/transit instead.
  218. if ( -x "/sbin/openrc" && "halt" ne $scriptname
  219. && "reboot" ne $scriptname ) {
  220. # no need to consider default disabled runlevels
  221. # because everything is disabled by openrc by default
  222. my @rls=script_runlevels($scriptname);
  223. system("rc-update", "add", $scriptname, openrc_rlconv(@rls))
  224. if ( @rls );
  225. }
  226. exit $rc;
  227. } else {
  228. error("initscript does not exist: /etc/init.d/$scriptname");
  229. }
  230. } elsif ("disable" eq $action || "enable" eq $action) {
  231. make_systemd_links($scriptname, $action);
  232. sysv_toggle($notreally, $action, $scriptname, @args);
  233. if ( ! -x $insserv) {
  234. # We are either under systemd or in a chroot where the link priorities don't matter
  235. systemd_reload;
  236. exit 0;
  237. }
  238. # Call insserv to resequence modified links
  239. my $rc = system($insserv, @opts, $scriptname) >> 8;
  240. if (0 == $rc && !$notreally) {
  241. save_last_action($scriptname, @orig_argv);
  242. }
  243. error_code($rc, "insserv rejected the script header") if $rc;
  244. systemd_reload;
  245. exit $rc;
  246. } else {
  247. usage();
  248. }
  249. }
  250. sub parse_def_start_stop {
  251. my $script = shift;
  252. my (%lsb, @def_start_lvls, @def_stop_lvls);
  253. open my $fh, '<', $script or error("unable to read $script");
  254. while (<$fh>) {
  255. chomp;
  256. if (m/^### BEGIN INIT INFO\s*$/) {
  257. $lsb{'begin'}++;
  258. }
  259. elsif (m/^### END INIT INFO\s*$/) {
  260. $lsb{'end'}++;
  261. last;
  262. }
  263. elsif ($lsb{'begin'} and not $lsb{'end'}) {
  264. if (m/^# Default-Start:\s*(\S?.*)$/) {
  265. @def_start_lvls = split(' ', $1);
  266. }
  267. if (m/^# Default-Stop:\s*(\S?.*)$/) {
  268. @def_stop_lvls = split(' ', $1);
  269. }
  270. }
  271. }
  272. close($fh);
  273. return (\@def_start_lvls, \@def_stop_lvls);
  274. }
  275. sub lsb_header_for_script {
  276. my $name = shift;
  277. foreach my $file ("/etc/insserv/overrides/$name", "/etc/init.d/$name",
  278. "/usr/share/insserv/overrides/$name") {
  279. return $file if -s $file;
  280. }
  281. error("cannot find a LSB script for $name");
  282. }
  283. sub cmp_args_with_defaults {
  284. my ($name, $act) = (shift, shift);
  285. my ($lsb_start_ref, $lsb_stop_ref, $arg_str, $lsb_str);
  286. my (@arg_start_lvls, @arg_stop_lvls, @lsb_start_lvls, @lsb_stop_lvls);
  287. ($lsb_start_ref, $lsb_stop_ref) = parse_def_start_stop("/etc/init.d/$name");
  288. @lsb_start_lvls = @$lsb_start_ref;
  289. @lsb_stop_lvls = @$lsb_stop_ref;
  290. return if (!@lsb_start_lvls and !@lsb_stop_lvls);
  291. warning "start and stop actions are no longer supported; falling back to defaults";
  292. my $start = $act eq 'start' ? 1 : 0;
  293. my $stop = $act eq 'stop' ? 1 : 0;
  294. # The legacy part of this program passes arguments starting with
  295. # "start|stop NN x y z ." but the insserv part gives argument list
  296. # starting with sequence number (ie. strips off leading "start|stop")
  297. # Start processing arguments immediately after the first seq number.
  298. my $argi = $_[0] eq $act ? 2 : 1;
  299. while (defined $_[$argi]) {
  300. my $arg = $_[$argi];
  301. # Runlevels 0 and 6 are always stop runlevels
  302. if ($arg eq 0 or $arg eq 6) {
  303. $start = 0; $stop = 1;
  304. } elsif ($arg eq 'start') {
  305. $start = 1; $stop = 0; $argi++; next;
  306. } elsif ($arg eq 'stop') {
  307. $start = 0; $stop = 1; $argi++; next;
  308. } elsif ($arg eq '.') {
  309. next;
  310. }
  311. push(@arg_start_lvls, $arg) if $start;
  312. push(@arg_stop_lvls, $arg) if $stop;
  313. } continue {
  314. $argi++;
  315. }
  316. if ($#arg_start_lvls != $#lsb_start_lvls or
  317. join("\0", sort @arg_start_lvls) ne join("\0", sort @lsb_start_lvls)) {
  318. $arg_str = @arg_start_lvls ? "@arg_start_lvls" : "none";
  319. $lsb_str = @lsb_start_lvls ? "@lsb_start_lvls" : "none";
  320. warning "start runlevel arguments ($arg_str) do not match",
  321. "$name Default-Start values ($lsb_str)";
  322. }
  323. if ($#arg_stop_lvls != $#lsb_stop_lvls or
  324. join("\0", sort @arg_stop_lvls) ne join("\0", sort @lsb_stop_lvls)) {
  325. $arg_str = @arg_stop_lvls ? "@arg_stop_lvls" : "none";
  326. $lsb_str = @lsb_stop_lvls ? "@lsb_stop_lvls" : "none";
  327. warning "stop runlevel arguments ($arg_str) do not match",
  328. "$name Default-Stop values ($lsb_str)";
  329. }
  330. }
  331. sub sysv_toggle {
  332. my ($dryrun, $act, $name) = (shift, shift, shift);
  333. my (@toggle_lvls, $start_lvls, $stop_lvls, @symlinks);
  334. my $lsb_header = lsb_header_for_script($name);
  335. # Extra arguments to disable|enable action are runlevels. If none
  336. # given parse LSB info for Default-Start value.
  337. if ($#_ >= 0) {
  338. @toggle_lvls = @_;
  339. } else {
  340. ($start_lvls, $stop_lvls) = parse_def_start_stop($lsb_header);
  341. @toggle_lvls = @$start_lvls;
  342. if ($#toggle_lvls < 0) {
  343. error("$name Default-Start contains no runlevels, aborting.");
  344. }
  345. }
  346. if ( -x "/sbin/openrc" ) {
  347. my %openrc_act = ( "disable" => "del", "enable" => "add" );
  348. system("rc-update", $openrc_act{$act}, $name,
  349. openrc_rlconv(@toggle_lvls))
  350. }
  351. # Find symlinks in rc.d directories. Refuse to modify links in runlevels
  352. # not used for normal system start sequence.
  353. for my $lvl (@toggle_lvls) {
  354. if ($lvl !~ /^[S2345]$/) {
  355. warning("$act action will have no effect on runlevel $lvl");
  356. next;
  357. }
  358. push(@symlinks, $_) for glob("/etc/rc$lvl.d/[SK][0-9][0-9]$name");
  359. }
  360. if (!@symlinks) {
  361. error("no runlevel symlinks to modify, aborting!");
  362. }
  363. # Toggle S/K bit of script symlink.
  364. for my $cur_lnk (@symlinks) {
  365. my $sk;
  366. my @new_lnk = split(//, $cur_lnk);
  367. if ("disable" eq $act) {
  368. $sk = rindex($cur_lnk, '/S') + 1;
  369. next if $sk < 1;
  370. $new_lnk[$sk] = 'K';
  371. } else {
  372. $sk = rindex($cur_lnk, '/K') + 1;
  373. next if $sk < 1;
  374. $new_lnk[$sk] = 'S';
  375. }
  376. if ($dryrun) {
  377. printf("rename(%s, %s)\n", $cur_lnk, join('', @new_lnk));
  378. next;
  379. }
  380. rename($cur_lnk, join('', @new_lnk)) or error($!);
  381. }
  382. }
  383. # Try to determine if initscripts is installed
  384. sub is_initscripts_installed {
  385. # Check if mountkernfs is available. We cannot make inferences
  386. # using the running init system because we may be running in a
  387. # chroot
  388. return glob('/etc/rcS.d/S??mountkernfs.sh');
  389. }