diff --git a/control.cmake b/control.cmake index 4a08152..4d01911 100644 --- a/control.cmake +++ b/control.cmake @@ -9,7 +9,7 @@ use warnings; our ($server_id, $mars_nwe_service, $smart_systemctl_path, $cc, %p); -sub html_escape($) +sub html_escape { my ($s) = @_; $s = '' unless defined $s; @@ -20,8 +20,137 @@ sub html_escape($) return $s; } -sub handle_request() +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 : ''; @@ -42,7 +171,7 @@ sub handle_request() $action = $1; } - print <

MARS_NWE service control

-EOF +HTML_HEAD if( $action eq '' ) { @@ -80,48 +213,85 @@ EOF 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 @cmd = ( $systemctl, $action, $service );
-    my $fh;
-    if( ! open( $fh, '-|', @cmd ) )
-    {
-        print 'Could not execute systemctl: ' . html_escape($!) . "\n";
-        print "

Failed.

\n"; - print "

Back

\n"; - print "
\n"; - return; - } + my $rc = 0; + my $output = ''; - while( my $line = <$fh> ) + if( $action eq 'status' ) { - print html_escape($line); - } - - close($fh); - my $rc = $? >> 8; - - if( $rc == 0 ) - { - print "

Command completed successfully.

\n"; + ( $rc, $output ) = run_systemctl_command( $systemctl, '--no-pager', '--full', 'status', $service ); + append_command_output( "systemctl --no-pager --full status $service", $rc, $output ); } else { - print "

Command failed with exit code $rc.

\n"; + # 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 ); } - print <

Command completed successfully.

\n"; + } + else + { + print "

Command failed with exit code $rc.

\n"; + } + + print < -Status -Start -Stop -Restart +Status +Start +Stop +Restart

Back

-EOF +HTML_FOOT } 1; diff --git a/settings.pl b/settings.pl index 7eedbdd..708a9a2 100644 --- a/settings.pl +++ b/settings.pl @@ -238,10 +238,7 @@ TT { color:#5b4b38; } -
- Back - Main menu -
+$settings_nav_bar
@@ -399,10 +396,7 @@ TT { color:#5b4b38; } -
- Back - Main menu -
+$settings_nav_bar
@@ -531,10 +525,7 @@ TT { color:#5b4b38; } -
- Back - Main menu -
+$settings_nav_bar
@@ -665,10 +656,7 @@ TT { color:#5b4b38; } -
- Back - Main menu -
+$settings_nav_bar
@@ -788,10 +776,7 @@ TT { color:#5b4b38; } -
- Back - Main menu -
+$settings_nav_bar
@@ -955,10 +940,7 @@ TT { color:#5b4b38; } -
- Back - Main menu -
+$settings_nav_bar
@@ -1197,10 +1179,7 @@ TT { color:#5b4b38; } -
- Back - Main menu -
+$settings_nav_bar
@@ -1325,10 +1304,7 @@ TT { color:#5b4b38; } -
- Back - Main menu -
+$settings_nav_bar
@@ -1498,10 +1474,7 @@ TT { color:#5b4b38; } -
- Back - Main menu -
+$settings_nav_bar
@@ -1586,10 +1559,7 @@ TT { color:#5b4b38; } -
- Back - Main menu -
+$settings_nav_bar
@@ -1704,10 +1674,7 @@ TT { color:#5b4b38; } -
- Back - Main menu -
+$settings_nav_bar
@@ -1813,10 +1780,7 @@ TT { color:#5b4b38; } -
- Back - Main menu -
+$settings_nav_bar
@@ -1931,10 +1895,7 @@ TT { color:#5b4b38; } -
- Back - Main menu -
+$settings_nav_bar
@@ -2053,10 +2014,7 @@ TT { color:#5b4b38; } -
- Back - Main menu -
+$settings_nav_bar
@@ -2164,10 +2122,7 @@ TT { color:#5b4b38; } -
- Back - Main menu -
+$settings_nav_bar
@@ -2269,10 +2224,7 @@ TT { color:#5b4b38; } -
- Back - Main menu -
+$settings_nav_bar
@@ -2358,10 +2310,7 @@ TT { color:#5b4b38; } -
- Back - Main menu -
+$settings_nav_bar
diff --git a/smart.cmake b/smart.cmake index 8846ffe..8ee6ce4 100644 --- a/smart.cmake +++ b/smart.cmake @@ -115,11 +115,63 @@ if( ( $c[0] eq 'service' && $c[1] eq 'control' ) || { # Service control must run before drop_root(). # /service/control is the preferred path; /cgi-bin/control is kept as a legacy alias. - my $rv = do( $smart_control_path ); - if( ! defined $rv ) + my @control_paths = (); + + if( defined( $smart_control_path ) && $smart_control_path ne '' && + $smart_control_path !~ /^\@.*\@/ ) { - error( 500 ); + push( @control_paths, $smart_control_path ); } + + push( @control_paths, $smart_libexec_dir . '/control' ); + + my %seen_control_path = (); + my $control_loaded = 0; + my $control_error = ''; + + foreach my $control_path ( @control_paths ) + { + next if $seen_control_path{$control_path}++; + next if $control_path eq ''; + + if( ! -r $control_path ) + { + $control_error .= "Cannot read control helper: $control_path: $!\n"; + next; + } + + my $rv = do( $control_path ); + if( defined( $rv ) ) + { + $control_loaded = 1; + last; + } + + if( $@ ne '' ) + { + $control_error .= "Could not compile control helper $control_path: $@\n"; + } + else + { + $control_error .= "Could not load control helper $control_path: $!\n"; + } + } + + if( ! $control_loaded ) + { + $control_error = 'Unknown control helper loading error.' if $control_error eq ''; + print <