[Pkg-systemd-maintainers] Bug#704197: Please review: systemd checks

Michael Stapelberg stapelberg at debian.org
Fri Mar 29 10:11:21 GMT 2013


Package: lintian
Version: 2.5.10.4
Severity: wishlist

Attached you can find my first stab at systemd-related checks for
lintian. While some details in parsing the service files are not
implemented (see the TODOs in the code), I’d like you to have a look at
the checks in general. Is there anything that needs to be improved
before these can be shipped with lintian?

Thanks!
-------------- next part --------------
Check-Script: systemd
Author: Michael Stapelberg <stapelberg at debian.org>
Abbrev: systemd
Type: binary
Info: Checks various systemd policy things
Needs-Info: scripts, index, unpacked, file-info

Tag: systemd-service-file-outside-lib
Severity: serious
Certainty: certain
Info: The package ships a systemd service file outside
 <tt>/lib/systemd/system/</tt>
 .
 System administrators should have the possibility to overwrite a service file
 (or parts of it, in newer systemd versions) by placing a file in
 <tt>/etc/systemd/system</tt>, so the canonical location we use for service
 files is <tt>/lib/systemd/system/</tt>.

Tag: systemd-tmpfiles.d-outside-usr-lib
Severity: serious
Certainty: certain
Info: The package ships a systemd tmpfiles.d(5) conf file outside
 <tt>/usr/lib/tmpfiles.d/</tt>

Tag: systemd-service-file-refers-to-obsolete-target
Severity: normal
Certainty: certain
Info: The systemd service file refers to an obsolete target.
 .
 Some targets are obsolete by now, e.g. syslog.target or dbus.target. For
 example, declaring <tt>After=syslog.target</tt> is unnecessary by now because
 syslog is socket-activated and will therefore be started when needed.

Tag: systemd-no-service-for-init-script
Severity: serious
Certainty: certain
Info: The listed init.d script has no systemd equivalent.
 .
 Systemd has a SysV init.d script compatibility mode. It provides access to
 each SysV init.d script as long as there is no native service file with the
 same name (e.g. <tt>/lib/systemd/system/rsyslog.service</tt> corresponds to
 <tt>/etc/init.d/rsyslog</tt>).
 .
 Your package ships a service file, but for the listed init.d script, there is
 no corresponding systemd service file.

Tag: init.d-script-does-not-source-init-functions
Severity: normal
Certainty: certain
Info: The <tt>/etc/init.d</tt> script does not source
 <tt>/lib/lsb/init-functions</tt>. The <tt>systemd</tt> package provides
 <tt>/lib/lsb/init-functions.d/40-systemd</tt> to redirect
 <tt>/etc/init.d/$script</tt> calls to systemctl.
 .
 Please add a line like this to your <tt>/etc/init.d</tt> script:
 .
  . /lib/lsb/init-functions

Tag: maintainer-script-calls-systemctl
Severity: normal
Certainty: certain
Info: The maintainer script calls systemctl directly. Actions such as enabling
 or starting a service have to be done via <tt>update-rc.d</tt> or
 <tt>invoke-rc.d</tt>, respectively, which both do the right thing when systemd
 is installed/running.
-------------- next part --------------
# systemd -- lintian check script -*- perl -*-
#
# Copyright © 2013 Michael Stapelberg
#
# based on the apache2 checks file by:
# Copyright © 2012 Arno Töll
#
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation; either version 2 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program.  If not, you can find it on the World Wide
# Web at http://www.gnu.org/copyleft/gpl.html, or write to the Free
# Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston,
# MA 02110-1301, USA.

package Lintian::systemd;

use strict;
use warnings;

use File::Basename;
use Lintian::Collect::Binary ();
use Lintian::Tags qw(tag);
use Lintian::Relation qw(:constants);
use Lintian::Util qw(fail);
use Data::Dumper;

sub run {
    my ($pkg, $type, $info) = @_;

    if ($type eq 'binary') {
        # Figure out whether the maintainer of this package did any effort to
        # make the package work with systemd. If not, we will not warn in case
        # of an init script that has no systemd equivalent, for example.
        my $ships_systemd_file = (scalar ( grep { m,/systemd/, } $info->sorted_index ) > 0);

        # An array of names which are provided by the service files.
        # This includes Alias= directives, so after parsing
        # NetworkManager.service, it will contain NetworkManager and
        # network-manager.
        my @systemd_targets;

        for my $file ($info->sorted_index) {
            if ($file =~ m,^etc/tmpfiles\.d/.*\.conf$,) {
                tag('systemd-tmpfiles.d-outside-usr-lib', $file);
            }
            if ($file =~ m,^etc/systemd/system/.*\.service$,) {
                tag('systemd-service-file-outside-lib', $file);
            }
            if ($file =~ m,/systemd/system/.*\.service$,) {
                check_systemd_service_file($pkg, $info, $file);
                for my $name (extract_service_file_names($pkg, $info, $file)) {
                    push @systemd_targets, $name;
                }
            }
        }

        my @init_scripts = grep(m,^etc/init\.d/.+,, $info->sorted_index);

        # Verify that each init script includes /lib/lsb/init-functions,
        # because that is where the systemd diversion happens.
        for my $init_script (@init_scripts) {
            check_init_script($pkg, $info, $init_script);
        }

        @init_scripts = map { basename($_) } @init_scripts;

        if ($ships_systemd_file) {
            for my $init_script (@init_scripts) {
                if (grep(/\Q$init_script\E\.service/, @systemd_targets) == 0) {
                    tag('systemd-no-service-for-init-script', $init_script);
                }
            }
        }

        check_maintainer_scripts($info);
    }
}

sub check_init_script {
    my ($pkg, $info, $file) = @_;
    
    my $lsb_source_seen;
    open(my $fh, '<', $info->unpacked($file));
    while (<$fh>) {
        s/^\s+//;
        next if /^#/;
        if (m,^(?:\.|source)\s+/lib/lsb/init-functions,) {
            $lsb_source_seen = 1;
            last;
        }
    }
    close($fh);

    if (!$lsb_source_seen) {
        tag('init.d-script-does-not-source-init-functions', $file);
    }
}

sub check_systemd_service_file {
    my ($pkg, $info, $file) = @_;

    my $filename =  $info->unpacked ($file);
    open(my $fh, '<', $filename);
    while (<$fh>) {
        if (/After.*((?:syslog|dbus).target)/) {
            tag('systemd-service-file-refers-to-obsolete-target', $file, $1);
        }
    }
    close($fh);
}

sub split_quoted {
    my ($input) = @_;

    my @result;

    while ($input ne '') {
        $input =~ s/^[ \t\n\r]+//;

        # by default, capture until whitespace
        my $end = "[ \t\n\r]";

        # or a specific delimiter, if present
        my ($begin) = ($input =~ /^([\'\"]?)/);
        $end = $begin if $begin ne '';

        $input =~ s/^$begin(.*?[^\\])(?:$end|$)//;
        push @result, $1;
    }

    return @result;
}

sub extract_service_file_names {
    my ($pkg, $info, $file) = @_;

    my @aliases;
    my $section;
    open(my $fh, '<', $info->unpacked($file));
    while (<$fh>) {
# TODO: continuation handling
        # equivalent of strstrip(l)
        $_ =~ s,[ \t\n\r]+$,,g;

        next if $_ eq '';

        next if /^[#;\n]/;

        # TODO: .include

        # section header
        if (/^\[([^\]]+)\]$/) {
            $section = $1;
            next;
        }

        if (!defined($section)) {
            # Assignment outside of section. Ignoring.
            next;
        }

        my ($key, $value) = ($_ =~ m,^(.*)=(.*)$,);
# TODO: where is the key comparison?
        if ($key eq 'Alias') {
            if ($value eq '') {
                # Empty assignment resets the list
                @aliases = ();
            }

            @aliases = (@aliases, split_quoted($value));
        }
    }
    close($fh);

    return (basename($file), @aliases);
}

sub check_maintainer_scripts {
    my ($info) = @_;

# TODO: use lab_data_path before submitting
    open my $fd, '<', $info->base_dir . '/control-scripts'
        or fail "cannot open lintian control-scripts file: $!";

    while (<$fd>) {
        m/^(\S*) (.*)$/ or fail("bad line in control-scripts file: $_");
        my $interpreter = $1;
        my $file = $2;
        my $filename = $info->control ($file);

        # Don't follow links
        next if -l $filename;
        # Don't try to parse the file if it does not appear to be a shell script
        next if $interpreter !~ m/sh\b/;

        open my $sfd, '<', $filename or fail "cannot open maintainer script $filename: $!";
        while (<$sfd>) {
            # skip comments
            next if substr ($_, 0, $-[0]) =~ /#/;

            # systemctl should not be called in maintainer scripts at all.
            if (m/\bsystemctl\b/) {
                tag('maintainer-script-calls-systemctl', $file);
            }
        }
        close $sfd;
    }

    close $fd;
}

1;

# Local Variables:
# indent-tabs-mode: nil
# cperl-indent-level: 4
# End:
# vim: syntax=perl sw=4 sts=4 sr et


More information about the Pkg-systemd-maintainers mailing list