diff --git a/ci/files.d/dbus.service b/ci/files.d/dbus.service new file mode 100644 index 0000000..0be95c4 --- /dev/null +++ b/ci/files.d/dbus.service @@ -0,0 +1,16 @@ +[Unit] +Description=D-Bus System Message Bus +Requires=dbus.socket +After=syslog.target + +[Service] +PIDFile=/var/run/messagebus.pid +ExecStartPre=/bin/mkdir -p /var/run/dbus +ExecStartPre=/bin/chmod g+w /var/run/ /var/run/dbus/ +ExecStart=/bin/dbus-daemon --system --fork +ExecReload=/bin/dbus-send --print-reply --system --type=method_call --dest=org.freedesktop.DBus / org.freedesktop.DBus.ReloadConfig +ExecStopPost=/bin/rm -f /var/run/messagebus.pid +OOMScoreAdjust=-900 +User=dbus +Group=root +PermissionsStartOnly=true diff --git a/ci/files.d/httpd.service b/ci/files.d/httpd.service new file mode 100644 index 0000000..ba7ec71 --- /dev/null +++ b/ci/files.d/httpd.service @@ -0,0 +1,4 @@ +.include /lib/systemd/system/httpd.service +[Service] +PIDFile=/var/run/httpd/httpd.pid + diff --git a/ci/files.d/systemctl b/ci/files.d/systemctl new file mode 100644 index 0000000..6a15c0b --- /dev/null +++ b/ci/files.d/systemctl @@ -0,0 +1,680 @@ +#!/usr/bin/perl + +# Copyright 2014 Jan Pazdziora +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +use strict; +use warnings FATAL => 'all'; + +use IO::File (); +use IO::Dir (); +use IO::Socket::UNIX (); +use Socket (); +use Data::Dumper (); +use POSIX (); +use Time::HiRes qw(sleep time); + +sub log_command { + local * LOG; + open(LOG, '>>', '/var/log/systemctl.log'); + print LOG @_; + close LOG; +} +log_command("[@ARGV]\n"); +shift if @ARGV and $ARGV[0] eq '-q'; + +for (my $i = 0; $i < @ARGV; $i++) { + if ($ARGV[$i] eq '--ignore-dependencies') { + splice @ARGV, $i, 1; + last; + } +} +if (@ARGV == 1 and $ARGV[0] eq 'daemon-reload') { + exit 0; +} +if (@ARGV == 2 and $ARGV[0] eq '--system' and $ARGV[1] eq 'daemon-reload') { + exit 0; +} + +for (keys %ENV) { + delete $ENV{$_} unless $_ eq '_SYSTEMCTL_LITE_STARTING'; +} + +my $RUNNING_DIR = '/run/systemctl-lite-running'; +if (not -d $RUNNING_DIR) { + mkdir $RUNNING_DIR; +} +my $ENABLED_DIR = '/etc/systemctl-lite-enabled'; +if (@ARGV == 1) { + if ($ARGV[0] eq 'start-enabled' and -d $ENABLED_DIR) { + local * ENABLED; + opendir ENABLED, $ENABLED_DIR; + my %services; + while (defined(my $f = readdir ENABLED)) { + next if $f eq '.' or $f eq '..'; + my $modified = (stat "$ENABLED_DIR/$f")[9]; + if (defined $modified) { + $services{$f} = $modified; + } + } + close ENABLED; + for my $s (sort { $services{$a} <=> $services{$b} or $a cmp $b } keys %services) { + print "Starting [$s]\n"; + system $0, 'start', $s; + exit ($? >> 8) if ($? >> 8); + } + } + if ($ARGV[0] eq 'stop-running' and -d $RUNNING_DIR) { + local * RUNNING; + opendir RUNNING, $RUNNING_DIR; + my %services; + while (defined(my $f = readdir RUNNING)) { + next if $f eq '.' or $f eq '..'; + my $modified = (stat "$RUNNING_DIR/$f")[9]; + if (defined $modified) { + my $trimmed = $f; + $trimmed =~ s/\..+$//; + $services{$trimmed} = $modified; + } + } + close RUNNING; + for my $s (sort { $services{$b} <=> $services{$a} or $a cmp $b } keys %services) { + system $0, 'stop', $s; + } + } + exit; +} + +if (@ARGV != 2) { + die "Usage: $0 (start|stop|status|...) (service|target|socket thing)\n"; +} + +my ($COMMAND, $SERVICE) = @ARGV; +my $TYPE = 'service'; +if ($SERVICE =~ /\.(target|socket)$/) { + $TYPE = $1; +} elsif (not $SERVICE =~ /\.service$/) { + $SERVICE .= '.service'; +} + +my @PATHS = ( + '/etc/systemd/system', + '/run/systemd/system', + '/usr/lib/systemd/system', +); + +my $FULL_SERVICE = $SERVICE; +my $INSTANCE = undef; + +my $file = undef; +if ($SERVICE =~ s/\@(.+)\.service$/\@.service/) { + $INSTANCE = $1; +} +for my $p (@PATHS) { + if (-e "$p/$SERVICE") { + if (-l "$p/$SERVICE") { + my $new_service = readlink("$p/$SERVICE"); + log_command("Service [$p/$SERVICE] is a symlink to [$new_service]\n"); + $new_service =~ s!^.*/!!; + $SERVICE = $new_service; + } + $file = "$p/$SERVICE"; + last; + } +} + +if ($SERVICE =~ s/\@(.+)\.service$/\@.service/) { + $INSTANCE = $1; + for my $p (@PATHS) { + if (-f "$p/$SERVICE") { + $file = "$p/$SERVICE"; + last; + } + } +} + +if (not defined $file) { + if ($COMMAND eq 'is-enabled') { + exit 1; + } + if ($COMMAND eq 'is-active') { + exit 3; + } + warn "No service definition found for [$FULL_SERVICE].\n"; + exit 2; +} + +sub parse_file { + my ($file, $data) = @_; + if (-d $file) { + my $op; + if ($file =~ /\.requires$/) { + $op = 'Unit.Requires'; + } elsif ($file =~ /\.wants$/) { + $op = 'Unit.Wants'; + } else { + die "Unknown directory [$file].\n"; + } + my $dh = new IO::Dir($file); + while (defined(my $de = $dh->read)) { + next if $de eq '.' or $de eq '..'; + push @{$data->{$op}}, $de unless $de =~ /\.target$/; + } + $dh->close; + return; + } + my $fh = new IO::File($file); + my $section = 'undefined'; + while (my $line = <$fh>) { + chomp $line; + if ($line =~ /^\[(.+)\]\s*$/) { + $section = $1; + next; + } + next if $line =~ /^\s*(#|$)/; + if ($line =~ /^\.include\s(.+)/) { + parse_file($1, $data); + next; + } + my ($key, $value) = split /=/, $line, 2; + if (defined $INSTANCE) { + $value =~ s/\%i/$INSTANCE/g; + } + if ($key eq 'EnvironmentFile') { + if ($value eq '') { + delete $data->{"$section.$key"}; + } else { + push @{ $data->{"$section.$key"} }, $value; + } + } elsif ($key =~ /^(Wants|Requires)$/) { + push @{ $data->{"$section.$key"} }, $value unless $value =~ /\.target$/; + } elsif ($key =~ /^(ExecStart(Pre|Post)|ExecReload|ExecStop(Pre|Post)?)$/) { + push @{ $data->{"$section.$key"} }, $value; + } else { + $data->{"$section.$key"} = $value; + } + } + $fh->close; + if (defined $data->{'Service.ExecStart'} and not defined $data->{'Service.PIDFile'}) { + my ($pidfile) = grep /^\/.+\.pid$/, split /\s+/, $data->{'Service.ExecStart'}; + if (defined $pidfile) { + $data->{'Service.PIDFile'} = $pidfile; + log_command("Guessing pid file [$data->{'Service.PIDFile'}] from ExecStart [$data->{'Service.ExecStart'}]\n"); + } + } +} + +my $data = {}; +parse_file($file, $data); +for my $p (@PATHS) { + if (-d "$p/$SERVICE.wants") { + $file = "$p/$SERVICE.wants"; + parse_file("$p/$SERVICE.wants", $data); + last; + } +} + +if ($COMMAND eq 'show') { + print Data::Dumper::Dumper $data; + exit; +} + +sub pidof { + my $command = shift; + my $pids = `/usr/sbin/pidof $command`; + chomp $pids; + if ($pids ne '') { + return split /\s+/, $pids; + } + return; +} + +sub get_exec_start { + my $data = shift; + my $d = $data->{'Service.ExecStart'}; + if (not defined $d) { + warn "No ExecStart value found for [$SERVICE].\n"; + exit 3; + } + return split /\s+/, $d; +} + +sub get_pid { + my $file = shift; + if (not $file =~ m!^/!) { + $file = "$RUNNING_DIR/$file"; + } + if (-f $file) { + local * PIDFILE; + open PIDFILE, '<', $file; + my $pid = ; + close PIDFILE; + if (defined $pid) { + chomp $pid; + return $pid; + } + } +} + +sub is_running { + my $data = shift; + my $ret; + if (-f "$RUNNING_DIR/$FULL_SERVICE.oneshot") { + return 1; + } elsif (defined $data->{'Service.PIDFile'}) { + my $pid = get_pid($data->{'Service.PIDFile'}); + if (defined $pid) { + $ret = kill 0, $pid; + } + } else { + my $path = get_pid("$FULL_SERVICE.name"); + if (defined $path) { + if (pidof($path)) { + $ret = 1; + } + } else { + my $pid = get_pid("$FULL_SERVICE.pid"); + if (defined $pid) { + $ret = kill 0, $pid; + } + } + } + return $ret; +} +if ($COMMAND eq 'is-active' or $COMMAND eq 'status') { + if (is_running($data)) { + print "active\n"; + exit; + } + print "inactive\n"; + exit 3; +} + +sub exec_stop_pre { + my ($data) = @_; + if (defined $data->{'Socket.ExecStopPre'}) { + for my $x (@{ $data->{'Socket.ExecStopPre'} }) { + log_command("Running stop pre [$x]\n"); + system $x; + } + } +} +sub exec_stop_post { + my ($data) = @_; + if (defined $data->{'Service.ExecStopPost'}) { + for my $x (@{ $data->{'Service.ExecStopPost'} }) { + log_command("Running stop post [$x]\n"); + system $x; + } + } +} + +if ($TYPE eq 'target' and not defined $data->{'Unit.Wants'} and not defined $data->{'Unit.Requires'}) { + warn "No Unit.Wants/.wants/Unit.Requires/.requires list for target [$SERVICE]\n"; + exit 8; +} + +if ($COMMAND eq 'restart') { + if ($TYPE ne 'service' or is_running($data)) { + system $0, 'stop', $FULL_SERVICE; + } + system $0, 'start', $FULL_SERVICE; + exit; +} + +sub pids_went_away_timeout { + my $timeout = shift; + my $start = time; + while (kill 0, @_) { + if (time - $start > $timeout) { + log_command(sprintf(" ** pid(s) [%s] not killed within %d s\n", join(', ', @_), $timeout)); + return 0; + } + sleep 0.1; + } + log_command(sprintf(" ** pid(s) [%s] got killed after %.2f s\n", join(', ', @_), time - $start)); + return 1; +} + +sub stop_pids { + my @pids = @_; + kill 15, @pids; + if (not pids_went_away_timeout(5, @pids)) { + kill 9, @pids; + if (not pids_went_away_timeout(5, @pids)) { + log_command("Failed to kill [@pids] even with 9\n"); + warn "Failed to kill [@pids].\n"; + return 1; + } + log_command("Killed [@pids] with 9\n"); + } else { + log_command("Killed [@pids] with 15\n"); + } + return 0; +} + +if ($COMMAND eq 'stop') { + my $mainpid; + if (defined $data->{'Service.PIDFile'}) { + $mainpid = get_pid($data->{'Service.PIDFile'}); + } else { + $mainpid = get_pid("$FULL_SERVICE.pid"); + } + if (defined $data->{'Service.ExecStop'}) { + for my $x (@{ $data->{'Service.ExecStop'} }) { + my $runit = 1; + if (defined $mainpid) { + $x =~ s!\$MAINPID\b|\$\{MAINPID\}!$mainpid!g; + } elsif ($x =~ /\$MAINPID\b|\$\{MAINPID\}/) { + $runit = 0; + log_command("Service [$FULL_SERVICE] would like to stop via ExecStop [$x] but we have no pid file, skipping.\n"); + } + if ($runit) { + log_command("Running stop [$x]\n"); + system $x; + sleep 1; + } + } + } + my $ret = undef; + if ($TYPE eq 'target') { + # noop + } elsif (defined $data->{'Service.Type'} and $data->{'Service.Type'} eq 'oneshot') { + # noop + } elsif ($TYPE eq 'socket') { + my $pids = `/usr/sbin/fuser $data->{'Socket.ListenStream'} 2> /dev/null`; + if (defined $pids) { + chomp $pids; + $pids =~ s/^\s+//; + my @pids = split /\s+/, $pids; + if (@pids) { + exec_stop_pre($data); + log_command("Will kill [@pids] as fuser [$data->{'Socket.ListenStream'}] of [$FULL_SERVICE]\n"); + $ret = stop_pids(@pids); + } + } + } elsif (defined $data->{'Service.PIDFile'}) { + my $pid = get_pid($data->{'Service.PIDFile'}); + if (defined $pid) { + log_command("Will kill [$pid] found in Service.PIDFile of [$FULL_SERVICE]\n"); + $ret = stop_pids($pid); + } + } else { + my $path = get_pid("$FULL_SERVICE.name"); + if (defined $path) { + if (my @pids = pidof($path)) { + log_command("Will kill [@pids] as pidof [$path] found in [$FULL_SERVICE.name]\n"); + $ret = stop_pids(@pids); + } else { + warn "No pidof for [$path] found in [$FULL_SERVICE.name].\n"; + } + } else { + my $pid = get_pid("$FULL_SERVICE.pid"); + if (defined $pid) { + log_command("Will kill [$pid] found in [$FULL_SERVICE.pid]\n"); + $ret = stop_pids($pid); + } else { + warn "No pid and no name for [$FULL_SERVICE].\n"; + } + } + } + exec_stop_post($data); + unlink "$RUNNING_DIR/$FULL_SERVICE.pid", "$RUNNING_DIR/$FULL_SERVICE.name", "$RUNNING_DIR/$FULL_SERVICE.oneshot"; + if (defined $data->{'Unit.Wants'}) { + for my $x (@{ $data->{'Unit.Wants'} }) { + system $0, 'stop', $x; + } + } + if (defined $data->{'Unit.Requires'}) { + for my $x (@{ $data->{'Unit.Requires'} }) { + system $0, 'stop', $x; + } + } + exit ( defined $ret ? $ret : 0 ); +} + +sub add_runuser { + my ($data, $cmd) = @_; + if (defined $data->{'Service.User'}) { + unshift @$cmd, '-u', $data->{'Service.User'}, '--'; + if (defined $data->{'Service.Group'}) { + unshift @$cmd, '-g', $data->{'Service.Group'}; + } + unshift @$cmd, '/usr/sbin/runuser'; + } +} + +if ($COMMAND eq 'start') { + my (@starting_stack, %starting_stack); + if (defined $ENV{_SYSTEMCTL_LITE_STARTING}) { + @starting_stack = split /:/, $ENV{_SYSTEMCTL_LITE_STARTING}; + @starting_stack{@starting_stack} = (); + $ENV{_SYSTEMCTL_LITE_STARTING} .= ":$FULL_SERVICE"; + } else { + $ENV{_SYSTEMCTL_LITE_STARTING} = $FULL_SERVICE; + } + + if ($TYPE eq 'service' and is_running($data)) { + log_command("Service [$FULL_SERVICE] already found running, not starting again.\n"); + exit; + } + if (defined $data->{'Service.PIDFile'}) { + log_command("Service [$FULL_SERVICE] defines PIDFile [$data->{'Service.PIDFile'}], unlinking it before start\n"); + unlink $data->{'Service.PIDFile'}; + } + if (defined $data->{'Unit.Wants'}) { + for my $x (@{ $data->{'Unit.Wants'} }) { + if (exists $starting_stack{$x}) { + log_command("Skipping start of [$x], we are already in the process of starting it.\n"); + next; + } + my @cmd = ($0, 'start', $x); + log_command("Running [@cmd] for Unit.Wants of [$FULL_SERVICE]\n"); + system @cmd; + } + } + if (defined $data->{'Unit.Requires'}) { + for my $x (@{ $data->{'Unit.Requires'} }) { + if (exists $starting_stack{$x}) { + log_command("Skipping start of [$x], we are already in the process of starting it.\n"); + next; + } + my @cmd = ($0, 'start', $x); + log_command("Running [@cmd] for Unit.Requires of [$FULL_SERVICE]\n"); + if (system @cmd) { + log_command("Failed to start [$x], aborting start\n"); + exit 1; + } + } + } + if (defined $data->{'Service.PIDFile'} and is_running($data)) { + log_command("Service [$FULL_SERVICE] defines PIDFile [$data->{'Service.PIDFile'}] and it seems to have already started, not starting again.\n"); + exit; + } + + if ($TYPE eq 'target') { + exit; + } + if ($TYPE eq 'socket' and -S $data->{'Socket.ListenStream'}) { + my $out = `/usr/sbin/fuser $data->{'Socket.ListenStream'} 2> /dev/null`; + if (defined $out and $out ne '') { + log_command("Service [$FULL_SERVICE] already found active on socket [$data->{'Socket.ListenStream'}], not starting again.\n"); + exit; + } + } + if (defined $data->{'Service.Type'} and $data->{'Service.Type'} eq 'dbus') { + my @cmd = ($0, 'start', 'dbus.socket'); + log_command("Running [@cmd] for Service.Type dbus\n"); + system @cmd; + } + if (defined $data->{'Service.ExecStartPre'}) { + for my $x (@{ $data->{'Service.ExecStartPre'} }) { + my $can_fail = 0; + if ($x =~ s/^-//) { + $can_fail = 1; + } + my @cmd = split /\s+/, $x; + if (not $data->{'Service.PermissionsStartOnly'} or $data->{'Service.PermissionsStartOnly'} =~ /^(false|0)$/i) { + add_runuser($data, \@cmd); + } + no warnings 'uninitialized'; + log_command("Running start pre [@cmd]\n"); + if (system @cmd and not $can_fail) { + exit 1; + } + } + } + my @paths; + if ($TYPE eq 'socket') { + my $service = $SERVICE; + if (defined $data->{'Socket.Accept'} and $data->{'Socket.Accept'} eq 'true') { + $service =~ s/\.socket$/\@.service/; + @paths = ( '/bin/systemctl-socket-daemon', $data->{'Socket.ListenStream'}, $data->{'Socket.SocketMode'} // '0666', $service ); + } else { + $service =~ s/\.socket$/\.service/; + my $ret = system "$0 is-active $service > /dev/null"; + if (($ret >> 8) == 0) { + log_command("Service [$service] is already running for [$SERVICE]\n"); + exit; + } + @paths = ( $0, 'start', $service ); + } + } else { + @paths = get_exec_start($data); + } + my $first_path = $paths[0]; + + my $ENV = ''; + if (exists $data->{'Service.EnvironmentFile'}) { + for my $e (@{ $data->{'Service.EnvironmentFile'} }) { + my $error_fail = 1; + if ($e =~ s/^-//) { + $error_fail = 0; + } + my $fh = new IO::File($e); + if (not defined $fh) { + if ($error_fail) { + warn "Error reading EnvironmentFile [$e]: $!\n"; + exit 5; + } + } else { + while (my $line = <$fh>) { + chomp $line; + next if $line =~ /^\s*(#|$)/; + $ENV .= "export $line; "; + } + $fh->close(); + } + } + } + if (defined $data->{'Service.Environment'}) { + my $x = $data->{'Service.Environment'}; + $x =~ s/^"(.*)"$/$1/; + $ENV .= "export $x; "; + } + add_runuser($data, \@paths); + log_command("Running [$ENV@paths]\n"); + if (defined $data->{'Service.Type'} and $data->{'Service.Type'} eq 'oneshot') { + system "$ENV@paths"; + if (defined $data->{'Service.RemainAfterExit'} and $data->{'Service.RemainAfterExit'} eq 'yes') { + local * PIDFILE; + open PIDFILE, '>', "$RUNNING_DIR/$FULL_SERVICE.oneshot"; + close PIDFILE; + } + exit; + } + my $pid = fork(); + die "Failed to fork for [@_]\n" if not defined $pid; + if ($pid == 0) { + open(STDOUT, '>>', '/var/log/systemctl.log'); + open(STDERR, '>>', '/var/log/systemctl.log'); + open(STDIN, '<', '/dev/null'); + POSIX::setsid(); + exec "$ENV@paths"; + } + if (defined $data->{'Service.PIDFile'}) { + log_command("Service [$FULL_SERVICE] defines PIDFile [$data->{'Service.PIDFile'}], not marking pid\n"); + unlink "$RUNNING_DIR/$FULL_SERVICE.pid"; + my $i = 0; + while (not -f $data->{'Service.PIDFile'}) { + sleep 0.2; + $i++; + if ($i > 250) { + log_command("Starting [$FULL_SERVICE] did not create PIDFile [$data->{'Service.PIDFile'}]\n"); + exit 10; + } + } + my $pid = get_pid($data->{'Service.PIDFile'}); + if (not defined $pid or not kill 0, $pid) { + log_command("Starting [$FULL_SERVICE] created PIDFile [$data->{'Service.PIDFile'}] but the process is not running\n"); + exit 11; + } + } else { + local * PIDFILE; + open PIDFILE, '>', "$RUNNING_DIR/$FULL_SERVICE.pid"; + print PIDFILE "$pid\n"; + close PIDFILE; + log_command("Marked pid [$pid] for [$FULL_SERVICE]\n"); + sleep 1; + if (defined $data->{'Service.Type'} and $data->{'Service.Type'} ne 'simple' and $data->{'Service.Type'} ne 'notify' and pidof($first_path)) { + local * PIDFILE; + open PIDFILE, '>', "$RUNNING_DIR/$FULL_SERVICE.name"; + print PIDFILE "$first_path\n"; + close PIDFILE; + log_command("Marked process name [$first_path] for [$FULL_SERVICE]\n"); + } + } + if (defined $data->{'Service.Type'} + and $data->{'Service.Type'} eq 'dbus' + and defined $data->{'Service.BusName'}) { + my $busname = $data->{'Service.BusName'}; + my $objectpath = $busname; + $objectpath =~ s!^|\.!/!g; + for (0 .. 10) { + system "/usr/bin/dbus-send --system --type=method_call --print-reply --dest=$busname $objectpath org.freedesktop.DBus.Introspectable.Introspect > /dev/null"; + exit if ($? >> 8) == 0; + sleep 1; + } + exit 9; + } + exit; +} + +if ($COMMAND eq 'is-enabled') { + if (-e "$ENABLED_DIR/$FULL_SERVICE") { + print "enabled\n"; + exit 0; + } + print "disabled\n"; + exit 1; +} + +if ($COMMAND eq 'enable') { + if (not -d $ENABLED_DIR) { + mkdir $ENABLED_DIR; + } + local * FILE; + open FILE, '>', "$ENABLED_DIR/$FULL_SERVICE"; + exit; +} + +if ($COMMAND eq 'disable') { + unlink "$ENABLED_DIR/$FULL_SERVICE"; + exit; +} + +die "Unknown command [$COMMAND].\n"; + +1; + diff --git a/ci/files.d/systemctl-socket-daemon b/ci/files.d/systemctl-socket-daemon new file mode 100644 index 0000000..5250cad --- /dev/null +++ b/ci/files.d/systemctl-socket-daemon @@ -0,0 +1,67 @@ +#!/usr/bin/perl + +# Copyright 2014 Jan Pazdziora +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +use strict; +use warnings FATAL => 'all'; + +use IO::Socket::UNIX (); +use Socket (); +use POSIX (); + +sub daemonize { + open(STDERR, '>>', '/var/log/systemctl-socket-daemon.log') || die "can't write log: $!"; + open(STDOUT, '>&STDERR') || die "can't write stdout to log: $!"; + chdir("/") || die "can't chdir to /: $!"; + open(STDIN, '<', '/dev/null') || die "can't read /dev/null: $!"; + defined(my $pid = fork()) || die "can't fork: $!"; + exit if $pid; # non-zero now means I am the parent + (POSIX::setsid() != -1) || die "Can't start a new session: $!"; +} + +my ($socket_path, $socket_mode, $service) = @ARGV; +if (not defined $socket_path or not defined $service) { + die "Usage: $0 /path/to/unix/socket service-to-run\n"; +} +if (-e $socket_path) { + warn "Path [$socket_path] already exists, removing\n"; + unlink $socket_path; +} + +my $service_data = `/bin/systemctl show $service 2>&1`; +if ($?) { + die "Failed to find service [$service]:\n$service_data"; +} + +my $socket = new IO::Socket::UNIX( + Type => Socket::SOCK_STREAM, + Local => $socket_path, + Listen => Socket::SOMAXCONN +) or die "socket: $!\n"; +chmod oct($socket_mode), $socket_path; + +daemonize(); + +while (1) { + next unless my $connection = $socket->accept; + my $pid = fork(); + if ($pid == 0) { + *STDIN = $connection; + *STDOUT = $connection; + exec '/bin/systemctl', 'start', $service; + die "exec should have never reached here\n"; + } +} +