diff options
author | Guillem Jover <guillem@debian.org> | 2017-09-17 12:18:15 +0200 |
---|---|---|
committer | Guillem Jover <guillem@debian.org> | 2017-09-24 21:03:10 +0200 |
commit | fca1bfe8406898105d1d724fb808f0cbcf985ae4 (patch) | |
tree | 7fc20ba945977bb370d3762f21c84fa1830ee93d | |
parent | b3608a01a8ac1416f0620bdf03d497c5b761b244 (diff) | |
download | dpkg-fca1bfe8406898105d1d724fb808f0cbcf985ae4.tar.gz |
dpkg-buildpackage: Add support for rootless builds
Implement the rootless-builds specification, by honoring the
Rules-Requires-Root (R³) field.
-rw-r--r-- | debian/changelog | 2 | ||||
-rw-r--r-- | man/deb-src-control.man | 33 | ||||
-rw-r--r-- | man/dpkg-buildpackage.man | 6 | ||||
-rw-r--r-- | scripts/Dpkg/Control/FieldsCore.pm | 5 | ||||
-rwxr-xr-x | scripts/dpkg-buildpackage.pl | 100 |
5 files changed, 132 insertions, 14 deletions
diff --git a/debian/changelog b/debian/changelog index 8d996abd8..078ad001b 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@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 1907eeed1..2f08ff7e5 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 2b5983e56..bf9c6ace1 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 f5c38d635..8f5d7f34a 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 2bdae7b18..affcca8d0 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') { |