Commit c9aa42db82 for openssl.org
commit c9aa42db82c3cf436e1e80481e7fb7eb345fb051
Author: Bob Beck <beck@openssl.org>
Date: Mon Aug 10 18:47:02 2026 -0600
Make most generated files read-only, and write them atomically
Generated files were left writable, so a hand edit looked like it had
worked and was silently lost at the next build. Only the pods and the
generated scripts were protected, by a chmod either side of the
recipe, which cost a shell and two processes for each file.
Route the generators that write to standard output through a helper
that collects the output, sets the mode and renames the result into
place. Renaming needs no permission on the file it replaces, so there
is no unlocking step, and a generator that fails part way now leaves
the previous file rather than a truncated one with a fresh timestamp
that make would believe.
The perlasm modules name their own output rather than writing to
standard output, so they are unchanged and their output stays
writable.
Reviewed-by: Neil Horman <nhorman@openssl.org>
Reviewed-by: Andrew Dinh <andrewd@openssl.org>
Reviewed-by: Tomas Mraz <tomas@openssl.foundation>
Merge-date: Tue Sep 1 14:03:12 2026
Merged-from: https://github.com/openssl/openssl/pull/32243
diff --git a/Configurations/unix-Makefile.tmpl b/Configurations/unix-Makefile.tmpl
index 6f0c8a602c..f8a5b1612e 100644
--- a/Configurations/unix-Makefile.tmpl
+++ b/Configurations/unix-Makefile.tmpl
@@ -1602,6 +1602,18 @@ EOF
my $defs = join("", map { " -D".$_ } @{$args{defs}});
my $deps = join(" ", compute_platform_depends(@{$args{generator_deps}},
@{$args{deps}}));
+ my $tofile = "\$(PERL) \$(SRCDIR)/util/file-from-stdout.pl";
+ # A generator named for more than one target cannot be producing them
+ # on standard output; it writes them itself, so its output must not be
+ # captured.
+ our %gen_targets;
+ unless (%gen_targets) {
+ foreach my $t (keys %{$unified_info{generate}}) {
+ $gen_targets{join(" ", @{$unified_info{generate}->{$t}})}++;
+ }
+ }
+ my $writes_own_files =
+ $gen_targets{join(" ", @{$args{generator}})} > 1;
if ($args{src} =~ /\.html$/) {
#
@@ -1651,7 +1663,8 @@ EOF
my $ord_name = $args{generator}->[1] || $args{product};
return <<"EOF";
$target: $gen0 $deps \$(SRCDIR)/util/mkdef.pl
- \$(PERL) \$(SRCDIR)/util/mkdef.pl$ord_ver --type $args{intent} --ordinals $gen0 --name $ord_name --OS $mkdef_os > $target
+ $tofile $target \\
+ \$(PERL) \$(SRCDIR)/util/mkdef.pl$ord_ver --type $args{intent} --ordinals $gen0 --name $ord_name --OS $mkdef_os
EOF
} elsif (platform->isasm($args{src})
|| platform->iscppasm($args{src})) {
@@ -1666,11 +1679,15 @@ EOF
} -> {$args{intent}};
my $generator;
+ # The perlasm modules take the output name as their last argument;
+ # m4 writes to standard output.
+ my $to_stdout = 0;
if ($gen0 =~ /\.pl$/) {
$generator = 'CC="$(CC)" $(PERL)'.$gen_incs.' '.$gen0.$gen_args
.' "$(PERLASM_SCHEME)"'.$incs.' '.$cppflags.$defs.' $(PROCESSOR)';
} elsif ($gen0 =~ /\.m4$/) {
- $generator = 'm4 -B 8192'.$gen_incs.' '.$gen0.$gen_args.' >'
+ $generator = 'm4 -B 8192'.$gen_incs.' '.$gen0.$gen_args;
+ $to_stdout = 1;
} elsif ($gen0 =~ /\.S$/) {
$generator = undef;
} else {
@@ -1678,15 +1695,17 @@ EOF
}
if (defined($generator)) {
+ my $recipe = $to_stdout ? "$tofile \$\@ $generator"
+ : "$generator \$\@";
return <<"EOF";
$args{src}: $gen0 $deps
- $generator \$@
+ $recipe
EOF
}
return <<"EOF";
$args{src}: $gen0 $deps
\$(CC) $incs $cppflags $defs -E $gen0 | \\
- \$(PERL) -ne '/^#(line)?\\s*[0-9]+/ or print' > \$@
+ $tofile \$@ \$(PERL) -ne '/^#(line)?\\s*[0-9]+/ or print'
EOF
} elsif ($gen0 =~ m|^.*\.in$|) {
#
@@ -1735,9 +1754,8 @@ EOF
return <<"EOF";
$args{src}: $gen0 $deps
- if [ -r "\$@" ]; then chmod u+w \$@; fi
- \$(PERL)$perlmodules "$dofile" "-o$target{build_file}" $gen0$gen_args > \$@
- chmod a-w \$@
+ $tofile \$@ \\
+ \$(PERL)$perlmodules "$dofile" "-o$target{build_file}" $gen0$gen_args
EOF
} elsif (grep { $_ eq $gen0 } @{$unified_info{programs}}) {
#
@@ -1750,7 +1768,9 @@ EOF
# Use $(PERL) to execute wrap.pl directly to avoid calling env
return <<"EOF";
$args{src}: $gen0 $deps \$(BLDDIR)/util/wrap.pl
- \$(PERL) \$(BLDDIR)/util/wrap.pl $gen0$gen_args > \$@
+ @{[ $writes_own_files
+ ? "\$(PERL) \$(BLDDIR)/util/wrap.pl $gen0$gen_args > \$\@"
+ : "$tofile \$\@ \$(PERL) \$(BLDDIR)/util/wrap.pl $gen0$gen_args" ]}
EOF
} else {
#
@@ -1758,7 +1778,9 @@ EOF
#
return <<"EOF";
$args{src}: $gen0 $deps
- \$(PERL)$gen_incs $gen0$gen_args > \$@
+ @{[ $writes_own_files
+ ? "\$(PERL)$gen_incs $gen0$gen_args > \$\@"
+ : "$tofile \$\@ \$(PERL)$gen_incs $gen0$gen_args" ]}
EOF
}
}
@@ -2117,11 +2139,9 @@ EOF
rel2abs($config{builddir}));
return <<"EOF";
$script: $sources configdata.pm
- if [ -r "$script" ]; then chmod u+w $script; fi
- \$(RM) "$script"
- \$(PERL) "-I\$(BLDDIR)" -Mconfigdata "$dofile" \\
- "-o$target{build_file}" $sources > "$script"
- chmod a+x,a-w $script
+ \$(PERL) \$(SRCDIR)/util/file-from-stdout.pl -x "$script" \\
+ \$(PERL) "-I\$(BLDDIR)" -Mconfigdata "$dofile" \\
+ "-o$target{build_file}" $sources
EOF
}
sub generatedir {
diff --git a/util/file-from-stdout.pl b/util/file-from-stdout.pl
new file mode 100644
index 0000000000..8a8180e229
--- /dev/null
+++ b/util/file-from-stdout.pl
@@ -0,0 +1,73 @@
+#! /usr/bin/env perl
+# Copyright 2026 The OpenSSL Project Authors. All Rights Reserved.
+#
+# Licensed under the Apache License 2.0 (the "License"). You may not use
+# this file except in compliance with the License. You can obtain a copy
+# in the file LICENSE in the source distribution or at
+# https://www.openssl.org/source/license.html
+
+use strict;
+use warnings;
+
+# Run a command and make its standard output into a file, in place of the
+# shell's ">". The output is collected in a temporary file and renamed over
+# the target once the command has succeeded, so a failing command leaves the
+# previous target rather than a truncated one with a fresh timestamp.
+#
+# The target is left read-only. Renaming onto a read-only file needs no
+# permission on the file itself, so it can still be replaced.
+#
+# Usage: file-from-stdout.pl [-x] FILE command [args...]
+
+my $executable = 0;
+
+if (@ARGV && $ARGV[0] eq "-x") {
+ $executable = 1;
+ shift @ARGV;
+}
+
+my $target = shift @ARGV;
+
+die "Usage: $0 [-x] FILE command [args...]\n"
+ unless defined $target && @ARGV;
+
+my $temp = "$target.tmp$$";
+
+unlink $temp;
+
+open my $out, ">", $temp
+ or die "Can't write $temp, $!\n";
+
+END {
+ unlink $temp if defined $temp && -e $temp;
+}
+
+open my $saved, ">&", \*STDOUT
+ or die "Can't save stdout, $!\n";
+open STDOUT, ">&", $out
+ or die "Can't redirect stdout to $temp, $!\n";
+
+my $status = system @ARGV;
+my $why = $!;
+
+open STDOUT, ">&", $saved
+ or die "Can't restore stdout, $!\n";
+close $saved;
+close $out
+ or die "Can't finish writing $temp, $!\n";
+
+if ($status != 0) {
+ my $how = $status == -1 ? "could not be run, $why"
+ : $status & 127 ? "died with signal " . ($status & 127)
+ : "exited with " . ($status >> 8);
+ my $code = $status == -1 || ($status & 127) ? 1 : $status >> 8;
+
+ print STDERR "$ARGV[0] $how\n";
+ exit $code;
+}
+
+chmod $executable ? 0555 : 0444, $temp
+ or die "Can't set the mode of $temp, $!\n";
+
+rename $temp, $target
+ or die "Can't rename $temp to $target, $!\n";
diff --git a/util/mkpod2html.pl b/util/mkpod2html.pl
index 0401dc7ced..15bd837805 100755
--- a/util/mkpod2html.pl
+++ b/util/mkpod2html.pl
@@ -70,6 +70,8 @@ sub format_page
$pod = rel2abs($pod) or die "Can't convert to real path: $!";
$out = rel2abs($out) or die "Can't convert to real path: $!";
+ unlink $out;
+
pod2html "--infile=$pod",
"--outfile=$out",
"--title=$title",
@@ -90,6 +92,9 @@ sub format_page
or die "Can't write $out, $!";
print $fh $contents;
close $fh;
+
+ chmod 0444, $out
+ or die "Can't set the mode of $out, $!\n";
}
# One named page: the output file and title are given.
diff --git a/util/mkpod2man.pl b/util/mkpod2man.pl
index 08903e4755..778dc2b091 100644
--- a/util/mkpod2man.pl
+++ b/util/mkpod2man.pl
@@ -75,12 +75,17 @@ sub format_page
# The page is current when it is newer than the pod it comes from.
return if -e $out && -M $out < -M $pod;
+ unlink $out;
+
Pod::Man->new(name => uc $name,
section => "$section$opt_m",
center => "OpenSSL",
date => $opt_d,
release => $opt_r)
->parse_from_file($pod, $out);
+
+ chmod 0444, $out
+ or die "Can't set the mode of $out, $!\n";
}
my @pods = @ARGV;