1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 395 396 397 398 399 400 401 402 403 404 405 406 407 408 409 410 411 412 413 414 415 416 417 418 419 420 421 422 423 424 425 426 427 428 429 430 431 432 433 434 435 436 437 438 439 440 441 442 443 444 445 446 447 448 449 450 451 452 453 454 455 456 457 458 459 460 461 462 463 464 465 466 467 468 469 470 471 472 473 474 475 476 477 478 479 480 481 482 483 484 485 486 487 488 489 490 491 492 493 494 495 496 497 498 499 500 501 502 503 504 505 506 507 508 509 510 511 512 513 514 515 516 517 518 519 520 521 522 523 524 525 526 527 528 529 530 531 532 533 534 535 536 537 538 539 540 541 542 543 544 545 546 547 548 549 550 551 552 553 554 555 556 557 558 559 560 561 562 563 564 565 566 567 568 569 570 571 572 573 574 575 576 577 578 579 580 581 582 583 584 585 586 587 588 589 590 591 592 593 594 595 596 597 598 599 600 601 602 603 604 605 606 607 608 609 610 611
|
#!/usr/bin/perl
# -*- fill-column: 78 -*-
# tag2upload-oracled -- tag2upload simple Oracle protocol communicator
# Copyright (C) 2024-2026 Sean Whitton
# Copyright (C) 2025-2026 Ian Jackson
#
# 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 3 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, see <https://www.gnu.org/licenses/>.
# usage:
# tag2upload-oracled [-D] [--force-production] \
# [--workers=WORKERS] [--no-restart-workers] \
# [--worker-restart-timeout=SECONDS] \
# [--processing-timeout=SECONDS] \
# [--ssh=SSH] \
# [--autopkgtest-virt=autopkgtest-virt-SERVER] \
# [--autopkgtest-arg=VIRT-SERVER-ARG] ... \
# [--retain-tmp] \
# --manager=[USER@]MNGR-HOST \
# --manager-socket=MNGR-SOCK \
# --builder=[USER@]BLDR-HOST \
# --from=FROM \
# --reply-to=REPLY-TO \
# --copies=COPIES \
# [--] DISTRO DISTRO-DIR AUTH-SPEC [<settings>]
# tag2upload-oracled --version
#
# Option -D may be repeated, e.g. -DDD, to increase the debug level.
# --processing-timeout=0 means no timeout, and is the default.
# If --version is present then all other arguments are ignored.
#
# Uses whatever one ambient gpg key is available.
use 5.028;
use warnings;
use POSIX qw(:errno_h :signal_h strftime WNOHANG);
use IPC::Open3;
use Symbol qw(gensym);
use URI::Escape;
use Getopt::Long;
use Fcntl qw(:flock);
use Debian::Dgit::Infra; # must precede Debian::Dgit
use Debian::Dgit qw(!fail);
use Debian::Dgit::ProtoConn;
sub fail ($);
sub test_signing_key ();
sub report_reaped_worker ($);
sub block_signals ();
sub unblock_signals();
sub get_dgit_version(@);
our ($production, $force_production) = (0, 0);
our ($workers_n, $restart_workers, $ssh, $adt_virt)
= (1, 1, "ssh", "autopkgtest-virt-null");
our ($processing_timeout, $worker_restart_timeout) = (0, 20);
our ($retain_tmp, $manager, $socket, $builder,
$from, $reply_to, $copies, $version_opt, @adt_args);
our $our_version = $ENV{DGIT_VERSION} // "UNRELEASED"; ###substituted###
our $our_version_str = "tag2upload-oracled version $our_version";
Getopt::Long::Configure "bundling";
GetOptions
# Optional arguments.
"D+" => \$debuglevel,
"force-production!" => \$force_production,
"workers=i" => \$workers_n,
"ssh=s" => \$ssh,
"autopkgtest-virt|adt-virt=s" => \$adt_virt,
"processing-timeout=i" => \$processing_timeout,
"worker-restart-timeout=i" => \$worker_restart_timeout,
"retain-tmp" => \$retain_tmp,
"autopkgtest-arg=s" => \@adt_args,
"restart-workers!" => \$restart_workers,
"version!" => \$version_opt,
# Required arguments.
"manager=s" => \$manager,
"manager-socket=s" => \$socket,
"builder=s" => \$builder,
"from=s" => \$from,
"reply-to=s" => \$reply_to,
"copies=s" => \$copies;
$manager && $socket && $builder && $from && $reply_to && $copies
|| $version_opt
or fail "not enough arguments";
if ($version_opt) {
say $our_version_str;
exit;
}
@ARGV >= 3 or fail "not enough arguments for dgit-repos-server";
our @drs_args = @ARGV;
initdebug "tag2upload-oracled ";
enabledebug if $debuglevel;
our @fatal_signals = qw(HUP TERM INT QUIT);
our $sigset
= POSIX::SigSet->new(map { no strict; &{"SIG$_"} } @fatal_signals);
our $signing_keyid;
our $production_string;
# We are expecting to be on a LAN with the Manager & Builder, so be fairly
# intolerant of connection issues.
our @ssh_opts = qw( -oBatchMode=yes -oConnectTimeout=30
-oServerAliveInterval=120 -oServerAliveCountMax=8 );
sub me () { (my $b = $builder) =~ s/^.+@//; "$b,$$" }
sub say_log (@) {
# We output to STDERR and let systemd pick it up for its journal.
# In the future, in addition, some kind of remote syslogging would be good
# so that we can inspect the live logs without shell access to the host
# running this daemon.
#
# Given these outputs, only do whole lines at once.
printf STDERR "[t2u-oracled %s][%s] %s\n",
me, strftime("%FT%T", gmtime), $_
for @_
}
sub fail ($) {
# Use this function in preference to using die() directly.
my $msg = shift;
$msg .= " at line ".(caller)[2];
$builder //= "none";
say_log "ERROR: $msg";
die $msg."\n";
}
sub warn_log ($) {
# Use this function in preference to warn().
say_log "WARNING: $_[0] at line ".(caller)[2];
}
# Main procedure.
{
# Decide whether we are a production or testing instance.
# The Manager should not send ordinary user jobs to a testing instance
# without manual intervention.
# Normally, only a clean install running everything out of dgit.deb and
# dgit-infrastructure.deb counts as a production instance.
# The output of systemctl's 'show' subcommand is a stable interface.
if ($force_production) {
$production = 1;
} elsif ($ENV{DBUS_SESSION_BUS_ADDRESS} && $ENV{XDG_RUNTIME_DIR}) {
my @wanted = qw(MainPID FragmentPath DropInPaths);
my $ret = open my $systemctl, "-|",
qw(systemctl --user show tag2upload-oracled.service),
map "--property=$_", @wanted;
if (!$ret) {
$! == ENOENT or fail "'systemctl show': $!";
} else {
chomp(my @lines = <$systemctl>);
close $systemctl
or fail "systemctl failed: ".failedcmd_waitstatus();
@lines == @wanted
or fail "unexpected number of systemctl output lines";
my %vals;
for (@lines) {
my ($k, $v) = split /=/, $_, 2;
exists $vals{$k}
and fail "unexpected systemctl output: repeated $k field";
$vals{$k} = $v // "";
}
$vals{$_} // fail "expected $_ in systemctl output" for @wanted;
$production = $vals{MainPID} == $$
&& $vals{FragmentPath} =~ m#^(?:/usr)?/lib/#
&& $vals{DropInPaths} eq "";
}
}
$production_string = $production ? "production" : "testing";
say_log sprintf "instance fidelity=%s", $production_string;
-d or mkdir or fail $! for "worker-cwd";
test_signing_key();
# WARNING! Be careful manipulating this without signals blocked!
# This variable is used by our signal handlers.
# (Right here is OK because we haven't set up the signal handlers yet.)
#
# Invariants:
# 1. Every one of our unreaped children is in this array,
# except briefly with signals blocked (while we're forking).
# 2. The converse is NOT true -- this may contain pids of
# workers that we have already reaped!
# 3. But *at the start of each iteration of the main loop*,
# it contains only (and therefore precisely) our unreaped children.
# 4. We reap only (a) in the main loop or (b) with signals blocked,
# in a signal handler which will definitely exit rather than return.
# Therefore code in the main loop can assume no children
# have been reaped other than by the main loop.
my @worker_slots = (undef)x$workers_n;
foreach my $sig (@fatal_signals) {
$SIG{$sig} = sub {
say_log "group_leader: received SIG$sig; shutting down workers";
# See the comment for @worker_slots, notably the invariants.
#
# We mustn't kill anything that isn't actually one of our
# children. @worker_slots might contain already-reaped pids.
# We can check a pid with waitpid, because we know that
# no-one else is reaping in between (given that we block signals).
#
# We might run this code more than once. So we might send
# multiple signals each child. That's OK and intended.
block_signals();
kill $sig => grep {
# waitpid returns:
# -1 Not our child, or doesn't exist. This is normal!
# >0 Was our child but we just reaped it.
# 0 Is our unfinished. unreaped, child..
# Only in the final case do we want to kill.
my $child = waitpid $_, WNOHANG;
if ($child > 0) {
fail "$child != $_" unless $child == $_;
report_reaped_worker($child);
# The pid remains in @worker_slots, despite being reaped.
# This is OK according to our invariants.
}
!$child
} grep defined, @worker_slots;
unblock_signals();
exit 0;
};
}
my $start_worker = sub {
# We're forking, and manipulating @worker_slots.
# Also, avoid entering our (parent-appropriate) signal handler in
# the child right after fork, before the child has reset %SIG.
block_signals();
my $free_slot;
for my $i (0..$#worker_slots) {
if (!defined $worker_slots[$i]) {
$free_slot = $i;
last;
}
}
$free_slot // fail "No free slot to start worker -- shouldn't happen";
if (my $child = fork // fail $!) {
$worker_slots[$free_slot] = $child;
unblock_signals();
} else {
$SIG{$_} = "DEFAULT" for @fatal_signals;
@worker_slots = (); # just in case
unblock_signals();
# Jump out of the parent process's lexical scope.
worker($free_slot);
# worker() should never return, but ensure no grandchild workers.
exit 255;
}
};
for (;;) {
# Particularly useful in the test suite: leaked oracleds will die.
stat '.' or fail "parent cwd has become inaccessibe: $!";
(stat _)[3] or fail "parent cwd deleted (link count 0), quitting";
# If we have empty worker slots, (re)start worker(s).
#
# We don't modify @worker_slots in this test,
# so this access with signals unblocked is OK.
$start_worker->() while grep !defined, @worker_slots;
# Now we do nothing until after at least one worker dies, then wait
# for a bit longer before going round again to start up a replacement.
# We start up one replacement at a time.
#
# If the worker died then it's probably because either the SSH
# connection failed, or there was a bug triggered by the particular
# manager request the worker was trying to handle. In both cases it
# is fine to restart workers: in the latter case, it's okay because no
# state is shared between workers, and the manager shouldn't send the
# bug-triggering request again immediately.
#
# In both cases, though, we want a delay. In the second case this is
# to prevent us getting stuck in a pointless tight forking loop if
# workers are dying over and over again in quick succession.
my $child = wait;
$child == -1 and fail "No workers to reap -- shouldn't be possible";
# We're manipulating worker_slots.
# We must block signals only now, *after* the wait,
# because we need such signals to interrupt the wait.
# Hence the possible presence of reaped pids in @worker_slots.
block_signals();
my $child_i;
for my $i (0..$#worker_slots) {
if ((defined $worker_slots[$i]) && $worker_slots[$i] == $child) {
$child_i = $i;
last;
}
}
if (defined $child_i) {
$worker_slots[$child_i] = undef;
unblock_signals();
report_reaped_worker($child);
# This could become more sophisticated (e.g. exponential backoff)
# if necessary, but hopefully things will be reliable enough.
fail "group leader: restarting workers disabled"
unless $restart_workers;
sleep $worker_restart_timeout;
} else {
unblock_signals();
say_log "group_leader: wait(2) returned unexpected PID $child";
}
}
}
sub worker ($) {
my $slot = shift;
# say_log will include our identity.
say_log "worker: new worker starting up";
# Try to establish a connection to the builder right away. If we can't,
# then we don't even want to make ourselves available to the manager.
my ($virt, $virt_dir, $virt_cmd_enclist, @virt_cmd, $virt_dgit_vstr);
my $run_cmd = sub {
# Check return value or $?, which are zero on success.
# Otherwise, use failedcmd_waitstatus to report the status.
$? = -1;
system $ssh, @ssh_opts, $builder, shellquote @virt_cmd, @_;
};
my $new_virt = sub {
# Use autopkgtest's virtualisation server protocol so that we can
# easily upgrade the isolation. Spec.:
# /usr/share/doc/autopkgtest/README.virtualisation-server.rst.gz
#
# The protocol requires that we ensure here, in this call to
# Debian::Dgit::ProtoConn::open2, that the way we invoke the
# virtualisation server will ensure that we have exclusive use of the
# testbed.
$virt = Debian::Dgit::ProtoConn->open2(
$ssh, @ssh_opts, $builder, $adt_virt, @adt_args);
$virt->set_description('virt');
$virt->set_fail_hook(sub {
(waitpid $virt->get_pid(), WNOHANG) == 0
or say_log "virt-server: ".waitstatusmsg;
});
$virt->expect(sub { /^ok$/ });
$virt->send("open");
($virt_dir) = $virt->expect(sub { /^ok (\S+)$/ });
$virt->send("print-execute-command");
($virt_cmd_enclist) = $virt->expect(sub { /^ok (\S+)/ });
@virt_cmd = map uri_unescape($_), split /,/, $virt_cmd_enclist;
$run_cmd->("true");
$? == 0 or fail "Cannot execute commands in builder virt: "
.failedcmd_waitstatus();
$virt_dgit_vstr = get_dgit_version $ssh, @ssh_opts, $builder,
shellquote @virt_cmd, $ENV{DGIT_DRS_DGIT} // "dgit", "--version";
say_log "worker: established builder virt environment";
};
my $quit_virt = sub {
unless ($retain_tmp) {
# Most virtualisation backends will take care of this, but
# it's not guaranteed by the protocol.
$run_cmd->(qw(rm -rf), $virt_dir);
$? == 0 or fail "failed to remove $virt_dir in builder virt: "
.failedcmd_waitstatus;
}
$virt->send("quit");
# Spec says we should expect `ok` but many autopkgtest-virt-*
# don't send it. #1092808. Anyway, we can safely waitpid without
# risk of deadlock -- the pipe would fit an ok if it sent one.
(waitpid $virt->get_pid(), 0) == $virt->get_pid() or fail $!;
fail sprintf "autopkgtest virt server: %s", waitstatusmsg() if $?;
undef $virt;
};
$new_virt->();
# Need our own cwd -- see dgit-repos-server's file header.
my $wcwd = "worker-cwd/w$slot";
-d or mkdir or fail $! for $wcwd;
chdir $wcwd or fail $!;
my $mngr = Debian::Dgit::ProtoConn->open2(
$ssh, @ssh_opts, $manager,
shellquote qw(nc.openbsd -U -N), $socket
);
$mngr->set_description('manager');
$mngr->set_fail_hook(
sub {
my $msg = shift;
(waitpid $mngr->get_pid(), WNOHANG) == 0
or say_log "worker: ssh to manager: ".waitstatusmsg;
eval { $mngr->send("protocol-violation $msg") };
say_log sprintf "worker: %s to inform manager: %s",
($@ ? "failed" : "attempted"), $msg;
});
$mngr->expect(sub { /^t2u-manager-ready$/ });
say_log "worker: established connection to Manager";
$mngr->send("t2u-oracle-version 8");
$mngr->send(sprintf "worker-id %s,w%s %s", me, $slot, $production_string);
for (;;) {
my ($msg, $payld_id, $payld_rs, $payld_pkg, $payld_url)
= $mngr->expect(sub {/^(?|
(ayt)
|(restart-worker)
|(job)
\ ([[:alnum:]][[:alnum:],-.]*)
\ (last-attempt|not-last-attempt)
\ ($package_re)
\ ([[:graph:]]+)
)$/ax});
if ($msg eq "ayt") {
# Check the connection to the builder is still up.
$virt->send("capabilities");
$virt->expect(sub { /^ok(?: |$)/ });
# Check the hardware token is still working.
test_signing_key();
my $orac_dgit_vstr
= get_dgit_version $ENV{DGIT_DRS_DGIT} // "dgit", "--version";
$mngr->send(sprintf "software-versions %s, Oracle %s, Builder %s",
$our_version_str, $virt_dgit_vstr, $orac_dgit_vstr);
$mngr->send("ack");
} elsif ($msg eq "restart-worker") {
$quit_virt->();
exit;
} elsif ($msg eq "job") {
my $last_attempt = $payld_rs eq "last-attempt";
my $tag = $mngr->receive_data_block;
my ($user_email) =
$mngr->expect(sub { /^user-email ([\t -\x7e]+)$/ });
my ($last_attempt_msg) =
$mngr->expect(sub { /^last-attempt-message (.+)$/ })
if $last_attempt;
my $lock_fail = sub {
my $msg = shift;
$mngr->send($_)
for "message $msg", "email unreported", "retriable";
fail $msg;
};
# Block fatal signals to avoid interrupting actual builds.
# (So not to protect @worker_slots -- we're the child.)
block_signals();
# Take DSA's reboot locks (RT ticket #9884) on both hosts to
# request waiting for this job to be complete before rebooting.
# If we can't get a lock immediately, assume that means a reboot
# is imminent, and so give up. The Manager will retry the job.
open my $oracle_flock, "<",
$ENV{DGIT_TEST_REBOOT_LOCK_1} // "/var/run/reboot-lock"
or $lock_fail->($!);
flock $oracle_flock, LOCK_SH|LOCK_NB or $lock_fail->($!);
my ($bflock_child, $bflock_in, $bflock_out);
my $bflock_err = gensym; # see IPC::Open3 docs
eval {
$bflock_child
= open3($bflock_in, $bflock_out, $bflock_err,
$ssh, @ssh_opts, $builder, shellquote
qw(flock --verbose -sn),
$ENV{DGIT_TEST_REBOOT_LOCK_2} // "/var/run/reboot-lock",
qw(sh -ec), "echo yes; read l")
};
$lock_fail->("Failed to start builder reboot lock script: $@")
if $@;
unless ($bflock_out && <$bflock_out> =~ /^yes$/) {
chomp(my @lines = <$bflock_err>);
say_log "builder flock(1) stderr: $_\n" for @lines;
@lines or @lines = ("<unknown error>");
$lock_fail->("Couldn't take builder reboot lock: "
.join " // ", @lines);
}
# warn_log() not fail() for locking issues from now on because
# there is no sense making the job irrecoverable if got this far.
close $bflock_out or warn_log $!;
handle_job($mngr, $virt_dir, $virt_cmd_enclist,
$payld_id, $user_email,
($last_attempt && $last_attempt_msg),
$tag, $payld_pkg, $payld_url);
# Release locks as soon as we're finished with the critical part.
print $bflock_in "\n" or warn_log $!;
(waitpid $bflock_child, 0) == $bflock_child or warn_log $!;
warn_log sprintf "builder reboot lock script %s", waitstatusmsg()
if $?;
close $oracle_flock or warn_log $!;
unblock_signals();
# Now stop the autopkgtest-virt-* process, and bring up another
# one. This means that we don't have to assume anything about
# what capabilities are available, which is more flexible.
$quit_virt->();
$new_virt->();
} else {
fail "ProtoConn's expect() has failed us";
}
}
}
sub handle_job ($$$$$$$$) {
my ($mngr, $virt_dir, $virt_cmd_enclist,
$id, $user_email, $retry_msg, $tag, $putative_pkg, $url) = @_;
# Parse it just enough to log something useful.
# Leave the real parsing, and emailing, to dgit-repos-server.
my ($tag_name) = $tag =~ /^tag (\S+)$/m or fail "couldn't find tag name";
my $log_info = sprintf "job=%s last_attempt=%d package=%s tag=%s",
$id, (defined $retry_msg), $putative_pkg, $tag_name;
say_log "$log_info url=$url starting";
# dgit-tmp is in trusted, and not in a .git, unlike elsewhere.
# This is a bit confusing but it means readtag etc. in dgit-repos-server
# can just use 'dgit-tmp' from their cwd.
rmdir_r "dgit-tmp";
mkdir "dgit-tmp" or fail $!;
# dgit-repos-server expects to find the tag here.
open my $wholetag_fh, ">dgit-tmp/wholetag" or fail $!;
print $wholetag_fh $tag;
close $wholetag_fh or fail $!;
# The diversion of the code path into dgit-repos-server now is for
# historical reasons. While invoking 'dgit rpush-source' is essential to
# the design, the parts of dgit-repos-server we use could be refactored
# and moved here.
my @drs
= ($ENV{DGIT_REPOS_SERVER_TEST} // qw(dgit-repos-server), @drs_args,
qw(--tag2upload11), $ssh, $builder, $virt_dir, $virt_cmd_enclist,
$from, $reply_to, $copies, $processing_timeout, $signing_keyid,
qw(--), $id, $url, $tag_name, $putative_pkg, $user_email,
$retry_msg // "");
say_log "worker: invoking <<@drs>>";
my $drs_child;
unless ($drs_child = fork // fail $!) {
# dgit-repos-server generates the remainder of the protocol messages.
# It needs both directions because, all being well, it will need to
# receive a 'go-ahead' from the Manager.
open STDIN, "<&=", $mngr->get_fh_r->fileno or fail $!;
open STDOUT, ">&=", $mngr->get_fh_w->fileno or fail $!;
exec @drs;
}
(waitpid $drs_child, 0) == $drs_child or fail $!;
fail sprintf "dgit-repos-server %s", waitstatusmsg() if $?;
}
sub test_signing_key () {
# debsign, which dgit-repos-server's dgit call will use, defaults
# to looking at the changelog to find a -u option to pass to gnupg,
# and there's no way to tell it to not pass any such option.
#
# Also it's probably a good idea to make sure that we're not implicitly
# doing something surprising.
#
# So, list our secret keys, and insist that there's exactly one,
# and pass its keyid to dgit-repos-server to pass to dgit to
# pass to debsign.
my $keys = cmdoutput qw(gpg --list-secret --with-colons);
my @keys = $keys =~ m{^fpr:.*}mg;
@keys or fail "no signing keys available";
@keys == 1 or fail "multiple signing keys available";
$signing_keyid = (split /:/, $keys[0])[9];
(defined $signing_keyid) && $signing_keyid =~ m{^[0-9a-f]+$}i
or fail "bad output from gnupg $keys[0]";
open my $gpg_in, "|gpg -u$signing_keyid --clearsign >/dev/null"
or fail $!;
print $gpg_in "Test of signing key.";
close $gpg_in
or fail "Signing key is not usable: ".failedcmd_waitstatus();
}
sub block_signals () { sigprocmask(SIG_BLOCK, $sigset) or fail $! }
sub unblock_signals () { sigprocmask(SIG_UNBLOCK, $sigset) or fail $! }
sub report_reaped_worker ($) {
# Logs a message about worker $pid, using $?.
# Doesn't update @worker_slots.
say_log sprintf "group_leader worker=%s: %s", shift, waitstatusmsg;
}
sub get_dgit_version (@) {
my @lines = split /\n/, cmdoutput @_;
@lines == 1
or fail "unexpected 'dgit --version' output: ".join " // ", @lines;
return $lines[0];
}
|