summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorGuillem Jover <guillem@debian.org>2017-09-17 12:18:15 +0200
committerGuillem Jover <guillem@debian.org>2017-09-24 21:03:10 +0200
commitfca1bfe8406898105d1d724fb808f0cbcf985ae4 (patch)
tree7fc20ba945977bb370d3762f21c84fa1830ee93d
parentb3608a01a8ac1416f0620bdf03d497c5b761b244 (diff)
downloaddpkg-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/changelog2
-rw-r--r--man/deb-src-control.man33
-rw-r--r--man/dpkg-buildpackage.man6
-rw-r--r--scripts/Dpkg/Control/FieldsCore.pm5
-rwxr-xr-xscripts/dpkg-buildpackage.pl100
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') {