#!/usr/bin/perl # # SMArT service control helper for MARS_NWE systemd unit. # Installed next to the other SMArT perl helpers. # use strict; use warnings; our ($server_id, $mars_nwe_service, $smart_systemctl_path, $cc, %p); sub html_escape { my ($s) = @_; $s = '' unless defined $s; $s =~ s/&/&/g; $s =~ s//>/g; $s =~ s/"/"/g; return $s; } sub run_systemctl_command { my ($systemctl, @args) = @_; my $output = ''; my ($reader, $writer); if( ! pipe( $reader, $writer ) ) { return (1, 'Could not create pipe: ' . $! . "\n"); } my $pid = fork(); if( ! defined( $pid ) ) { close( $reader ); close( $writer ); return (1, 'Could not fork: ' . $! . "\n"); } if( $pid == 0 ) { close( $reader ); # Capture stdout for the web UI. Leave stderr untouched so systemctl # warnings/errors still go to smart.log, matching the old behaviour. open( STDOUT, '>&', $writer ) or exit 127; close( $writer ); exec( $systemctl, @args ) or do { print 'Could not execute ' . $systemctl . ': ' . $! . "\n"; exit 127; }; } close( $writer ); while( my $line = <$reader> ) { $output .= $line; } close( $reader ); waitpid( $pid, 0 ); my $rc = $? >> 8; return ( $rc, $output ); } sub wait_for_service_state { my ($systemctl, $service, $wanted, $timeout) = @_; my $elapsed = 0; my $last_state = ''; my $ok = 0; $timeout = 45 unless defined($timeout) && $timeout > 0; print '# waiting for ' . html_escape($service) . ' to become ' . html_escape($wanted) . "\n"; while( $elapsed <= $timeout ) { my ($state_rc, $state_output) = run_systemctl_command( $systemctl, 'is-active', $service ); my $state = $state_output; $state =~ s/[\r\n]+$//; $state =~ s/^\s+//; $state =~ s/\s+$//; $state = 'unknown' if $state eq ''; if( $state ne $last_state ) { print sprintf( "[%2ds] state: %s\n", $elapsed, html_escape($state) ); $last_state = $state; } if( $wanted eq 'inactive' ) { if( $state eq 'inactive' || $state eq 'failed' ) { $ok = 1; last; } } elsif( $wanted eq 'active' ) { if( $state eq 'active' ) { $ok = 1; last; } if( $state eq 'failed' ) { last; } } sleep( 1 ); $elapsed++; } if( $ok ) { print "wait result: reached $wanted\n"; return 0; } print "wait result: timeout after $timeout seconds\n"; return 1; } sub append_command_output { my ($title, $rc, $output) = @_; print '# ' . html_escape($title) . "\n"; if( defined($output) && $output ne '' ) { print html_escape($output); } else { print "(no output)\n"; } print "exit code: $rc\n"; } sub handle_request { local $| = 1; my $service = $mars_nwe_service || '@MARS_NWE_SYSTEMD_SERVICE@'; my $systemctl = $smart_systemctl_path || '@SYSTEMCTL_EXECUTABLE@'; my $query = defined($cc) ? $cc : ''; my $action = ''; $query =~ s/^\?//; if( $query =~ /^(start|stop|restart|status)$/ ) { $action = $1; } elsif( $query =~ /(?:^|&)action=(start|stop|restart|status)(?:&|$)/ ) { $action = $1; } elsif( defined($p{action}) && $p{action} =~ /^(start|stop|restart|status)$/ ) { $action = $1; } print < MARS_NWE service control

MARS_NWE service control

HTML_HEAD if( $action eq '' ) { print "

Invalid action.

\n"; print "

Allowed actions: start, stop, restart, status

\n"; print "

Back

\n"; print "
\n"; return; } print '
Action: ' . html_escape($action) . '
' . "\n"; print 'Service: ' . html_escape($service) . '
' . "\n"; print 'systemctl: ' . html_escape($systemctl) . '
' . "\n"; if( $action ne 'status' ) { my $wait_text = 'Waiting for service state change'; if( $action eq 'stop' ) { $wait_text = 'Stopping service, waiting until it is inactive'; } elsif( $action eq 'start' ) { $wait_text = 'Starting service, waiting until it is active'; } elsif( $action eq 'restart' ) { $wait_text = 'Restarting service, waiting until it is active'; } print '
' . "\n"; print '' . "\n"; print '
' . html_escape($wait_text) . '
' . "\n"; print '
This can take up to 45 seconds. Please wait until the final status appears below.
' . "\n"; print '
' . "\n"; print " " x 4096; # help browsers render the waiting box before the command finishes } print "
\n";

    my $rc = 0;
    my $output = '';

    if( $action eq 'status' )
    {
        ( $rc, $output ) = run_systemctl_command( $systemctl, '--no-pager', '--full', 'status', $service );
        append_command_output( "systemctl --no-pager --full status $service", $rc, $output );
    }
    else
    {
        # Avoid repeated "unit file changed on disk" warnings after install/update.
        my ( $reload_rc, $reload_output ) = run_systemctl_command( $systemctl, 'daemon-reload' );
        append_command_output( 'systemctl daemon-reload', $reload_rc, $reload_output );
        print "\n";

        ( $rc, $output ) = run_systemctl_command( $systemctl, $action, $service );
        append_command_output( "systemctl $action $service", $rc, $output );
        print "\n";

        if( $rc == 0 )
        {
            my $wanted_state = ( $action eq 'stop' ) ? 'inactive' : 'active';
            my $wait_rc = wait_for_service_state( $systemctl, $service, $wanted_state, 45 );
            print "\n";
            $rc = $wait_rc if $wait_rc != 0;
        }

        my ( $status_rc, $status_output ) = run_systemctl_command( $systemctl, '--no-pager', '--full', 'status', $service );
        append_command_output( "systemctl --no-pager --full status $service", $status_rc, $status_output );
    }

    if( $rc == 0 )
    {
        print "

Command completed successfully.

\n"; } else { print "

Command failed with exit code $rc.

\n"; } print < Status Start Stop Restart

Back

HTML_FOOT } 1;