[dpkg] 133/192: dpkg-buildpackage: Add support for rootless builds

Ximin Luo infinity0 at debian.org
Tue Oct 17 11:04:10 UTC 2017


This is an automated email from the git hooks/post-receive script.

infinity0 pushed a commit to branch pu/reproducible_builds
in repository dpkg.

commit fca1bfe8406898105d1d724fb808f0cbcf985ae4
Author: Guillem Jover <guillem at debian.org>
Date:   Sun Sep 17 12:18:15 2017 +0200

    dpkg-buildpackage: Add support for rootless builds
    
    Implement the rootless-builds specification, by honoring the
    Rules-Requires-Root (R³) field.
---
 debian/changelog                   |   2 +
 man/deb-src-control.man            |  33 ++++++++++++
 man/dpkg-buildpackage.man          |   6 ++-
 scripts/Dpkg/Control/FieldsCore.pm |   5 ++
 scripts/dpkg-buildpackage.pl       | 100 ++++++++++++++++++++++++++++++++-----
 5 files changed, 132 insertions(+), 14 deletions(-)

diff --git a/debian/changelog b/debian/changelog
index 8d996ab..078ad00 100644
--- a/debian/changelog
+++ b/debian/changelog
@@ -21,6 +21,8 @@ dpkg (1.19.0) UNRELEASED; urgency=medium
     Based on a patch by Niels Thykier <niels at thykier.net>. Closes: #291320
   * Make dpkg-buildpackage error out if --as-root is passed without
     --rules-target.
+  * Add support for rootless builds in dpkg-buildpackage by honoring the
+    Rules-Requires-Root (R³) field.
   * Perl modules:
     - Switch from Dpkg::Util to List::Util, now that the module in the
       new required Perl contains the needed functions.
diff --git a/man/deb-src-control.man b/man/deb-src-control.man
index 1907eee..2f08ff7 100644
--- a/man/deb-src-control.man
+++ b/man/deb-src-control.man
@@ -82,6 +82,39 @@ used format is \fIbts-type\fP\fB://\fP\fIbts-address\fP, like
 \fBdebbugs://bugs.debian.org\fP. This field is usually not needed.
 
 .TP
+.BR Rules\-Requires\-Root: " \fBno\fP|\fBbinary-targets\fP|\fIimpl-keywords\fP
+This field is used to indicate whether the \fBdebian/rules\fP file requires
+(fake)root privileges to run some of its targets, and if so when.
+.RS
+.TP
+.B no
+The binary targets will not require (fake)root at all.
+.TP
+.B binary\-targets
+The binary targets must always be run under (fake)root.
+This value is the default when the field is omitted; adding the field
+with an explicit \fBbinary\-targets\fP while not strictly needed, marks
+it as having been analyzed for this requirement.
+.TP
+.I impl-keywords
+This is a space-separated list of keywords which define when (fake)root
+is required.
+
+Keywords consist of \fInamespace\fP/\fIcases\fP.
+The \fInamespace\fP part cannot contain "/" or whitespace.
+The \fIcases\fP part cannot contain whitespace.
+Furthermore, both parts must consist entirely of printable ASCII characters.
+
+Each tool/package will define a namespace named after itself and provide
+a number of cases where (fake)root is required.
+(See "Implementation provided keywords" in \fIrootless-builds.txt\fP).
+
+When the field is set to one of the \fIimpl-keywords\fP, the builder will
+expose an interface that is used to run a command under (fake)root.
+(See "Gain Root API" in \fIrootless-builds.txt\fP.)
+.RE
+
+.TP
 .BI Vcs\-Arch: " url"
 .TQ
 .BI Vcs\-Bzr: " url"
diff --git a/man/dpkg-buildpackage.man b/man/dpkg-buildpackage.man
index 2b5983e..bf9c6ac 100644
--- a/man/dpkg-buildpackage.man
+++ b/man/dpkg-buildpackage.man
@@ -513,7 +513,11 @@ standalone should be supported.
 \fBdpkg\-architecture\fP is called with the \fB\-a\fP and \fB\-t\fP
 parameters forwarded. Any variable that is output by its \fB\-s\fP
 option is integrated in the build environment.
-
+.TP
+.B DPKG_GAIN_ROOT_CMD
+This variable is set to \fIgain-root-command\fP when the field
+\fBRules\-Requires\-Root\fP is set to a value different to \fBno\fP and
+\fBbinary-targets\fP.
 .TP
 .B SOURCE_DATE_EPOCH
 This variable is set to the Unix timestamp since the epoch of the
diff --git a/scripts/Dpkg/Control/FieldsCore.pm b/scripts/Dpkg/Control/FieldsCore.pm
index f5c38d6..8f5d7f3 100644
--- a/scripts/Dpkg/Control/FieldsCore.pm
+++ b/scripts/Dpkg/Control/FieldsCore.pm
@@ -416,6 +416,11 @@ our %FIELDS = (
         allowed => CTRL_TESTS,
         separator => FIELD_SEP_SPACE,
     },
+    'rules-requires-root' => {
+        name => 'Rules-Requires-Root',
+        allowed => CTRL_INFO_SRC,
+        separator => FIELD_SEP_SPACE,
+    },
     'section' => {
         name => 'Section',
         allowed => CTRL_INFO_SRC | CTRL_INDEX_SRC | ALL_PKG,
diff --git a/scripts/dpkg-buildpackage.pl b/scripts/dpkg-buildpackage.pl
index 2bdae7b..affcca8 100755
--- a/scripts/dpkg-buildpackage.pl
+++ b/scripts/dpkg-buildpackage.pl
@@ -178,6 +178,12 @@ my $changedby;
 my $desc;
 my @buildinfo_opts;
 my @changes_opts;
+my %target_legacy_root = map { $_ => 1 } qw(
+    clean binary binary-arch binary-indep
+);
+my %target_official =  map { $_ => 1 } qw(
+    clean build build-arch build-indep binary binary-arch binary-indep
+);
 my @hook_names = qw(
     init preclean source build binary buildinfo changes postclean check sign done
 );
@@ -431,6 +437,7 @@ my $cwd = cwd();
 my $dir = basename($cwd);
 
 my $changelog = changelog_parse();
+my $ctrl = Dpkg::Control::Info->new();
 
 my $pkg = mustsetvar($changelog->{source}, g_('source package'));
 my $version = mustsetvar($changelog->{version}, g_('source version'));
@@ -455,6 +462,10 @@ if ($changedby) {
 # <https://reproducible-builds.org/specs/source-date-epoch/>
 $ENV{SOURCE_DATE_EPOCH} ||= $changelog->{timestamp} || time;
 
+# Check whether we are doing some kind of rootless build, and sanity check
+# the fields values.
+my %rules_requires_root = parse_rules_requires_root($ctrl);
+
 my @arch_opts;
 push @arch_opts, ('--host-arch', $host_arch) if $host_arch;
 push @arch_opts, ('--host-type', $host_type) if $host_type;
@@ -538,20 +549,14 @@ if ($checkbuilddep) {
 }
 
 foreach my $call_target (@call_target) {
-    if ($call_target_as_root or
-        $call_target =~ /^(clean|binary(|-arch|-indep))$/)
-    {
-        run_cmd(@rootcommand, @debian_rules, $call_target);
-    } else {
-        run_cmd(@debian_rules, $call_target);
-    }
+    run_rules_cond_root($call_target);
 }
 exit 0 if scalar @call_target;
 
 run_hook('preclean', ! $noclean);
 
 unless ($noclean) {
-    run_cmd(@rootcommand, @debian_rules, 'clean');
+    run_rules_cond_root('clean');
 }
 
 run_hook('source', build_has_any(BUILD_SOURCE));
@@ -568,14 +573,16 @@ run_hook('build', build_has_any(BUILD_BINARY));
 
 # XXX Use some heuristics to decide whether to use build-{arch,indep} targets.
 # This is a temporary measure to not break too many packages on a flag day.
-build_target_fallback();
+build_target_fallback($ctrl);
 
 my $build_types = get_build_options_from_type();
 
 if (build_has_any(BUILD_BINARY)) {
-    run_cmd(@debian_rules, $buildtarget);
+    # If we are building rootless, there is no need to call the build target
+    # independently as non-root.
+    run_cmd(@debian_rules, $buildtarget) if rules_requires_root($buildtarget);
     run_hook('binary', 1);
-    run_cmd(@rootcommand, @debian_rules, $binarytarget);
+    run_rules_cond_root($binarytarget);
 }
 
 run_hook('buildinfo', 1);
@@ -607,7 +614,7 @@ close $changes_fh or subprocerr(g_('dpkg-genchanges'));
 run_hook('postclean', $cleansource);
 
 if ($cleansource) {
-    run_cmd(@rootcommand, @debian_rules, 'clean');
+    run_rules_cond_root('clean');
 }
 
 chdir('..') or syserr('chdir ..');
@@ -678,11 +685,73 @@ sub mustsetvar {
     return $var;
 }
 
+sub parse_rules_requires_root {
+    my $ctrl = shift;
+
+    my %rrr;
+    my $rrr = $ctrl->{'Rules-Requires-Root'} // 'binary-targets';
+    my $keywords_base;
+    my $keywords_impl;
+
+    foreach my $keyword (split ' ', $rrr) {
+        if ($keyword =~ m{/}) {
+            if ($keyword =~ m{^dpkg/target/(.*)$}p and $target_official{$1}) {
+                error(g_('disallowed target in %s field keyword %s'),
+                      'Rules-Requires-Root', $keyword);
+            } elsif ($keyword ne 'dpkg/target-subcommand') {
+                error(g_('unknown %s field keyword %s in dpkg namespace'),
+                      'Rules-Requires-Root', $keyword);
+            }
+            $keywords_impl++;
+        } else {
+            if ($keyword ne 'no' and $keyword ne 'binary-targets') {
+                warning(g_('unknown %s field keyword %s'),
+                        'Rules-Requires-Root', $keyword);
+            }
+            $keywords_base++;
+        }
+
+        if ($rrr{$keyword}++) {
+            error(g_('field %s contains duplicate keyword %s'),
+                        'Rules-Requires-Root', $keyword);
+        }
+    }
+
+    if ($keywords_base > 1 or $keywords_base and $keywords_impl) {
+        error(g_('%s field contains both global and implementation specific keywords'),
+              'Rules-Requires-Root');
+    } elsif ($keywords_impl) {
+        # Set only on <implementations-keywords>.
+        $ENV{DPKG_GAIN_ROOT_CMD} = join ' ', @rootcommand;
+    }
+
+    return %rrr;
+}
+
 sub run_cmd {
     printcmd(@_);
     system @_ and subprocerr("@_");
 }
 
+sub rules_requires_root {
+    my $target = shift;
+
+    return 1 if $call_target_as_root;
+    return 1 if $rules_requires_root{"dpkg/target/$target"};
+    return 1 if $rules_requires_root{'binary-targets'} and $target_legacy_root{$target};
+    return 0;
+}
+
+sub run_rules_cond_root {
+    my $target = shift;
+
+    my @cmd;
+    push @cmd, @rootcommand if rules_requires_root($target);
+    push @cmd, @debian_rules, $target;
+
+    run_cmd(@cmd);
+}
+
 sub run_hook {
     my ($name, $enabled) = @_;
     my $cmd = $hook{$name};
@@ -787,13 +856,18 @@ sub describe_build {
 }
 
 sub build_target_fallback {
+    my $ctrl = shift;
+
+    # If we are building rootless, there is no need to call the build target
+    # independently as non-root.
+    return if not rules_requires_root($buildtarget);
+
     return if $buildtarget eq 'build';
     return if scalar @debian_rules != 1;
 
     # Check if we are building both arch:all and arch:any packages, in which
     # case we now require working build-indep and build-arch targets.
     my $pkg_arch = 0;
-    my $ctrl = Dpkg::Control::Info->new();
 
     foreach my $bin ($ctrl->get_packages()) {
         if ($bin->{Architecture} eq 'all') {

-- 
Alioth's /usr/local/bin/git-commit-notice on /srv/git.debian.org/git/reproducible/dpkg.git



More information about the Reproducible-commits mailing list